Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/NEWS b/NEWS
index 7e484b4b8..503ae7734 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4319 +1,4319 @@
Noteworthy changes in version 2.3.0 (unreleased)
------------------------------------------------
Changes also found in 2.2.19:
* gpg: Only in 2.2.19; not requird in master: Fix double free when
decrypting for hidden recipients. Regression in 2.2.18. [#4762].
* gpg: Use auto-key-locate for encryption even for mail addresses
given with angle brackets. [#4726]
* gpgsm: Add special case for certain expired intermediate
certificates. [#4696]
Release-info: https://dev.gnupg.org/T4768
See-also: gnupg-announce/2019q4/000443.html
Changes also found in 2.2.18:
* gpg: Changed the way keys are detected on a smartcards; this
allows the use of non-OpenPGP cards. In the case of a not very
likely regression the new option --use-only-openpgp-card is
available. [#4681]
* gpg: The commands --full-gen-key and --quick-gen-key now allow
direct key generation from supported cards. [#4681]
* gpg: Prepare against chosen-prefix SHA-1 collisions in key
signatures. This change removes all SHA-1 based key signature
from the web-of-trust. Note that this includes all key signature
created with dsa1024 keys. (Version 2.2.18 limits this to key
signatures newer than 2019-01-19.) The new option
--allow-weak-key-signatues can be used to override the new and
safer behaviour. [#4755,CVE-2019-14855]
* gpg: Improve performance for import of large keyblocks. [#4592]
* gpg: Implement a keybox compression run. [#4644]
* gpg: Show warnings from dirmngr about redirect and certificate
problems (details require --verbose as usual).
* gpg: Allow to pass the empty string for the passphrase if the
'--passphase=' syntax is used. [#4633]
* gpg: Fix printing of the KDF object attributes.
* gpg: Avoid surprises with --locate-external-key and certain
--auto-key-locate settings. [#4662]
* gpg: Improve selection of best matching key. [#4713]
* gpg: Delete key binding signature when deleting a subkey.
[#4665,#4457]
* gpg: Fix a potential loss of key signatures during import with
self-sigs-only active. [#4628]
* gpg: Silence "marked as ultimately trusted" diagnostics if
option --quiet is used. [#4634]
* gpg: Silence some diagnostics during in key listsing even with
option --verbose. [#4627]
* gpg, gpgsm: Change parsing of agent's pkdecrypt results. [#4652]
* gpgsm: Support AES-256 keys.
* gpgsm: Fix a bug in triggering a keybox compression run if
--faked-system-time is used.
* dirmngr: System CA certificates are no longer used for the SKS
pool if GNUTLS instead of NTBTLS is used as TLS library. [#4594]
* dirmngr: On Windows detect usability of IPv4 and IPv6 interfaces
to avoid long timeouts. [#4165]
* scd: Fix BWI value for APDU level transfers to make Gemalto Ezio
Shield and Trustica Cryptoucan work. [#4654,#4566]
* wkd: gpg-wks-client --install-key now installs the required policy
file.
Release-info: https://dev.gnupg.org/T4684
See-also: gnupg-announce/2019q4/000442.html
Changes also found in 2.2.17:
* gpg: Ignore all key-signatures received from keyservers. This
change is required to mitigate a DoS due to keys flooded with
faked key-signatures. The old behaviour can be achieved by adding
keyserver-options no-self-sigs-only,no-import-clean
to your gpg.conf. [#4607]
* gpg: If an imported keyblocks is too large to be stored in the
keybox (pubring.kbx) do not error out but fallback to an import
using the options "self-sigs-only,import-clean". [#4591]
* gpg: New command --locate-external-key which can be used to
refresh keys from the Web Key Directory or via other methods
configured with --auto-key-locate.
* gpg: New import option "self-sigs-only".
* gpg: In --auto-key-retrieve prefer WKD over keyservers. [#4595]
* dirmngr: Support the "openpgpkey" subdomain feature from
draft-koch-openpgp-webkey-service-07. [#4590].
* dirmngr: Add an exception for the "openpgpkey" subdomain to the
CSRF protection. [#4603]
* dirmngr: Fix endless loop due to http errors 503 and 504. [#4600]
* dirmngr: Fix TLS bug during redirection of HKP requests. [#4566]
* gpgconf: Fix a race condition when killing components. [#4577]
Release-info: https://dev.gnupg.org/T4606
See-also: gnupg-announce/2019q3/000439.html
Changes also found in 2.2.16:
* gpg,gpgsm: Fix deadlock on Windows due to a keybox sharing
violation. [#4505]
* gpg: Allow deletion of subkeys with --delete-key. This finally
makes the bang-suffix work as expected for that command. [#4457]
* gpg: Replace SHA-1 by SHA-256 in self-signatures when updating
them with --quick-set-expire or --quick-set-primary-uid. [#4508]
* gpg: Improve the photo image viewer selection. [#4334]
* gpg: Fix decryption with --use-embedded-filename. [#4500]
* gpg: Remove hints on using the --keyserver option. [#4512]
* gpg: Fix export of certain secret keys with comments. [#4490]
* gpg: Reject too long user-ids in --quick-gen-key. [#4532]
* gpg: Fix a double free in the best key selection code. [#4462]
* gpg: Fix the key generation dialog for switching back from EdDSA
to ECDSA.
* gpg: Use AES-192 with SHA-384 to comply with RFC-6637.
* gpg: Use only the addrspec from the Signer's UID subpacket to
mitigate a problem with another implementation.
* gpg: Skip invalid packets during a keyring listing and sync
diagnostics with the output.
* gpgsm: Avoid confusing diagnostic when signing with the default
key. [#4535]
* agent: Do not delete any secret key in --dry-run mode.
* agent: Fix failures on 64 bit big-endian boxes related to URIs in
a keyfile. [#4501]
* agent: Stop scdaemon after a reload with disable-scdaemon newly
configured. [#4326]
* dirmngr: Improve caching algorithm for WKD domains.
* dirmngr: Support other hash algorithms than SHA-1 for OCSP. [#3966]
* gpgconf: Make --homedir work for --launch. [#4496]
* gpgconf: Before --launch check for a valid config file. [#4497]
* wkd: Do not import more than 5 keys from one WKD address.
* wkd: Accept keys which are stored in armored format in the
directory.
* The installer for Windows now comes with signed binaries.
Release-info: https://dev.gnupg.org/T4509
See-also: gnupg-announce/2019q2/000438.html
Changes also found in 2.2.15:
* sm: Fix --logger-fd and --status-fd on Windows for non-standard
file descriptors.
* sm: Allow decryption even if expired keys are configured. [#4431]
* agent: Change command KEYINFO to print ssh fingerprints with other
hash algos.
* dirmngr: Fix build problems on Solaris due to the use of reserved
symbol names. [#4420]
* wkd: New commands --print-wkd-hash and --print-wkd-url for
gpg-wks-client.
Release-info: https://dev.gnupg.org/T4434
See-also: gnupg-announce/2019q1/000436.html
Changes also found in 2.2.14:
* gpg: Allow import of PGP desktop exported secret keys. Also avoid
importing secret keys if the secret keyblock is not valid. [#4392]
* gpg: Make invalid primary key algo obvious in key listings.
* sm: Do not mark a certificate in a key listing as de-vs compliant
if its use for a signature will not be possible.
* sm: Fix certificate creation with key on card.
* sm: Create rsa3072 bit certificates by default.
* sm: Print Yubikey attestation extensions with --dump-cert.
* agent: Fix cancellation handling for scdaemon.
* agent: Support --mode=ssh option for CLEAR_PASSPHRASE. [#4340]
* scd: Fix flushing of the CA-FPR DOs in app-openpgp.
* scd: Avoid a conflict error with the "undefined" app.
* dirmngr: Add CSRF protection exception for protonmail.
* dirmngr: Fix build problems with gcc 9 in libdns.
* gpgconf: New option --show-socket for use with --launch.
* gpgtar: Make option -C work for archive creation.
Release-info: https://dev.gnupg.org/T4412
See-also: gnupg-announce/2019q1/000435.html
Changes also found in 2.2.13:
* gpg: Implement key lookup via keygrip (using the & prefix).
* gpg: Allow generating Ed25519 key from existing key.
* gpg: Emit an ERROR status line if no key was found with -k.
* gpg: Stop early when trying to create a primary Elgamal key. [#4329]
* gpgsm: Print the card's key algorithms along with their keygrips
in interactive key generation.
* agent: Clear bogus pinentry cache in the error case. [#4348]
* scd: Support "acknowledge button" feature.
* scd: Fix for USB INTERRUPT transfer. [#4308]
* wks: Do no use compression for the the encrypted challenge and
response.
Release-info: https://dev.gnupg.org/T4290
See-also: gnupg-announce/2019q1/000434.html
Changes also found in 2.2.12:
* tools: New commands --install-key and --remove-key for
gpg-wks-client. This allows to prepare a Web Key Directory on a
local file system for later upload to a web server.
* gpg: New --list-option "show-only-fpr-mbox". This makes the use
of the new gpg-wks-client --install-key command easier on Windows.
* gpg: Improve processing speed when --skip-verify is used.
* gpg: Fix a bug where a LF was accidentally written to the console.
* gpg: --card-status now shows whether a card has the new KDF
feature enabled.
* agent: New runtime option --s2k-calibration=MSEC. New configure
option --with-agent-s2k-calibration=MSEC. [#3399]
* dirmngr: Try another keyserver from the pool on receiving a 502,
503, or 504 error. [#4175]
* dirmngr: Avoid possible CSRF attacks via http redirects. A HTTP
query will not anymore follow a 3xx redirect unless the Location
header gives the same host. If the host is different only the
host and port is taken from the Location header and the original
path and query parts are kept.
* dirmngr: New command FLUSHCRL to flush all CRLS from disk and
memory. [#3967]
* New simplified Chinese translation (zh_CN).
Release-info: https://dev.gnupg.org/T4289
See-also: gnupg-announce/2018q4/000433.html
Changes also found in 2.2.11:
- * gpgsm: Fix CRL loading when intermediate certicates are not yet
+ * gpgsm: Fix CRL loading when intermediate certificates are not yet
trusted.
* gpgsm: Fix an error message about the digest algo. [#4219]
* gpg: Fix a wrong warning due to new sign usage check introduced
with 2.2.9. [#4014]
* gpg: Print the "data source" even for an unsuccessful keyserver
query.
* gpg: Do not store the TOFU trust model in the trustdb. This
allows to enable or disable a TOFO model without triggering a
trustdb rebuild. [#4134]
* scd: Fix cases of "Bad PIN" after using "forcesig". [#4177]
* agent: Fix possible hang in the ssh handler. [#4221]
* dirmngr: Tack the unmodified mail address to a WKD request. See
commit a2bd4a64e5b057f291a60a9499f881dd47745e2f for details.
* dirmngr: Tweak diagnostic about missing LDAP server file.
* dirmngr: In verbose mode print the OCSP responder id.
* dirmngr: Fix parsing of the LDAP port. [#4230]
* wks: Add option --directory/-C to the server. Always build the
server on Unix systems.
* wks: Add option --with-colons to the client. Support sites which
use the policy file instead of the submission-address file.
* Fix EBADF when gpg et al. are called by broken CGI scripts.
* Fix some minor memory leaks and bugs.
Release-info: https://dev.gnupg.org/T4233
See-also: gnupg-announce/2018q4/000432.html
Changes also found in 2.2.10:
* gpg: Refresh expired keys originating from the WKD. [#2917]
* gpg: Use a 256 KiB limit for a WKD imported key.
* gpg: New option --known-notation. [#4060]
* scd: Add support for the Trustica Cryptoucan reader.
* agent: Speed up starting during on-demand launching. [#3490]
* dirmngr: Validate SRV records in WKD queries.
Release-info: https://dev.gnupg.org/T4112
See-also: gnupg-announce/2018q3/000428.html
Changes also found in 2.2.9:
* dirmngr: Fix recursive resolver mode and other bugs in the libdns
code. [#3374,#3803,#3610]
* dirmngr: When using libgpg-error 1.32 or later a GnuPG build with
NTBTLS support (e.g. the standard Windows installer) does not
anymore block for dozens of seconds before returning data.
* gpg: Fix bug in --show-keys which actually imported revocation
certificates. [#4017]
* gpg: Ignore too long user-ID and comment packets. [#4022]
* gpg: Fix crash due to bad German translation. Improved printf
format compile time check.
* gpg: Handle missing ISSUER sub packet gracefully in the presence of
the new ISSUER_FPR. [#4046]
* gpg: Allow decryption using several passphrases in most cases.
[#3795,#4050]
* gpg: Command --show-keys now enables the list options
show-unusable-uids, show-unusable-subkeys, show-notations and
show-policy-urls by default.
* gpg: Command --show-keys now prints revocation certificates. [#4018]
* gpg: Add revocation reason to the "rev" and "rvs" records of the
option --with-colons. [#1173]
* gpg: Export option export-clean does now remove certain expired
subkeys; export-minimal removes all expired subkeys. [#3622]
* gpg: New "usage" property for the drop-subkey filters. [#4019]
Release-info: https://dev.gnupg.org/T4036
See-also: gnupg-announce/2018q3/000427.html
Changes also found in 2.2.8:
* gpg: Decryption of messages not using the MDC mode will now lead
to a hard failure even if a legacy cipher algorithm was used. The
option --ignore-mdc-error can be used to turn this failure into a
warning. Take care: Never use that option unconditionally or
without a prior warning.
* gpg: The MDC encryption mode is now always used regardless of the
cipher algorithm or any preferences. For testing --rfc2440 can be
used to create a message without an MDC.
* gpg: Sanitize the diagnostic output of the original file name in
verbose mode. [#4012,CVE-2018-12020]
* gpg: Detect suspicious multiple plaintext packets in a more
reliable way. [#4000]
* gpg: Fix the duplicate key signature detection code. [#3994]
* gpg: The options --no-mdc-warn, --force-mdc, --no-force-mdc,
--disable-mdc and --no-disable-mdc have no more effect.
* gpg: New command --show-keys.
* agent: Add DBUS_SESSION_BUS_ADDRESS and a few other envvars to the
list of startup environment variables. [#3947]
See-also: gnupg-announce/2018q2/000425.html
Changes also found in 2.2.7:
* gpg: New option --no-symkey-cache to disable the passphrase cache
for symmetrical en- and decryption.
* gpg: The ERRSIG status now prints the fingerprint if that is part
of the signature.
* gpg: Relax emitting of FAILURE status lines
* gpg: Add a status flag to "sig" lines printed with --list-sigs.
* gpg: Fix "Too many open files" when using --multifile. [#3951]
* ssh: Return an error for unknown ssh-agent flags. [#3880]
* dirmngr: Fix a regression since 2.1.16 which caused corrupted CRL
caches under Windows. [#2448,#3923]
* dirmngr: Fix a CNAME problem with pools and TLS. Also use a fixed
mapping of keys.gnupg.net to sks-keyservers.net. [#3755]
* dirmngr: Try resurrecting dead hosts earlier (from 3 to 1.5 hours).
* dirmngr: Fallback to CRL if no default OCSP responder is configured.
* dirmngr: Implement CRL fetching via https. Here a redirection to
http is explicitly allowed.
* dirmngr: Make LDAP searching and CRL fetching work under Windows.
This stopped working with 2.1. [#3937]
* agent,dirmngr: New sub-command "getenv" for "getinfo" to ease
debugging.
See-also: gnupg-announce/2018q2/000424.html
Changes also found in 2.2.6:
* gpg,gpgsm: New option --request-origin to pretend requests coming
from a browser or a remote site.
* gpg: Fix race condition on trustdb.gpg updates due to too early
released lock. [#3839]
* gpg: Emit FAILURE status lines in almost all cases. [#3872]
* gpg: Implement --dry-run for --passwd to make checking a key's
passphrase straightforward.
* gpg: Make sure to only accept a certification capable key for key
signatures. [#3844]
* gpg: Better user interaction in --card-edit for the factory-reset
sub-command.
* gpg: Improve changing key attributes in --card-edit by adding an
explicit "key-attr" sub-command. [#3781]
* gpg: Print the keygrips in the --card-status.
* scd: Support KDF DO setup. [#3823]
* scd: Fix some issues with PC/SC on Windows. [#3825]
* scd: Fix suspend/resume handling in the CCID driver.
* agent: Evict cached passphrases also via a timer. [#3829]
* agent: Use separate passphrase caches depending on the request
origin. [#3858]
* ssh: Support signature flags. [#3880]
* dirmngr: Handle failures related to missing IPv6 support
gracefully. [#3331]
* Fix corner cases related to specified home directory with
drive letter on Windows. [#3720]
* Allow the use of UNC directory names as homedir. [#3818]
See-also: gnupg-announce/2018q2/000421.html
Changes also found in 2.2.5:
* gpg: Allow the use of the "cv25519" and "ed25519" short names in
addition to the canonical curve names in --batch --gen-key.
* gpg: Make sure to print all secret keys with option --list-only
and --decrypt. [#3718]
* gpg: Fix the use of future-default with --quick-add-key for
signing keys. [#3747]
* gpg: Select a secret key by checking availability under gpg-agent.
[#1967]
* gpg: Fix reversed prompt texts for --only-sign-text-ids. [#3787]
* gpg,gpgsm: Fix detection of bogus keybox blobs on 32 bit systems.
[#3770]
* gpgsm: Fix regression since 2.1 in --export-secret-key-raw which
got $d mod (q-1)$ wrong. Note that most tools automatically fixup
that parameter anyway.
* ssh: Fix a regression in getting the client'd PID on *BSD and
macOS.
* scd: Support the KDF Data Object of the OpenPGP card 3.3. [#3152]
* scd: Fix a regression in the internal CCID driver for certain card
readers. [#3508]
* scd: Fix a problem on NetBSD killing scdaemon on gpg-agent
shutdown. [#3778]
* dirmngr: Improve returned error description on failure of DNS
resolving. [#3756]
* wks: Implement command --install-key for gpg-wks-server.
* Add option STATIC=1 to the Speedo build system to allow a build
with statically linked versions of the core GnuPG libraries. Also
use --enable-wks-tools by default by Speedo builds for Unix.
See-also: gnupg-announce/2018q1/000420.html
Changes also found in 2.2.4:
* gpg: Change default preferences to prefer SHA512.
* gpg: Print a warning when more than 150 MiB are encrypted using a
cipher with 64 bit block size.
* gpg: Print a warning if the MDC feature has not been used for a
message.
* gpg: Fix regular expression of domain addresses in trust
signatures. [#2923]
* agent: New option --auto-expand-secmem to help with high numbers
of concurrent connections. Requires libgcrypt 1.8.2 for having
an effect. [#3530]
* dirmngr: Cache responses of WKD queries.
* gpgconf: Add option --status-fd.
* wks: Add commands --check and --remove-key to gpg-wks-server.
* Increase the backlog parameter of the daemons to 64 and add
option --listen-backlog.
* New configure option --enable-run-gnupg-user-socket to first try a
socket directory which is not removed by systemd at session end.
See-also: gnupg-announce/2017q4/000419.html
Changes also found in 2.2.3:
* gpgsm: Fix initial keybox creation on Windows. [#3507]
* dirmngr: Fix crash in case of a CRL loading error. [#3510]
* Fix the name of the Windows registry key. [Git#4f5afaf1fd]
* gpgtar: Fix wrong behaviour of --set-filename. [#3500]
* gpg: Silence AKL retrieval messages. [#3504]
* agent: Use clock or clock_gettime for calibration. [#3056]
* agent: Improve robustness of the shutdown pending
state. [Git#7ffedfab89]
See-also: gnupg-announce/2017q4/000417.html
Changes also found in 2.2.2:
* gpg: Avoid duplicate key imports by concurrently running gpg
processes. [#3446]
* gpg: Fix creating on-disk subkey with on-card primary key. [#3280]
* gpg: Fix validity retrieval for multiple keyrings. [Debian#878812]
* gpg: Fix --dry-run and import option show-only for secret keys.
* gpg: Print "sec" or "sbb" for secret keys with import option
import-show. [#3431]
* gpg: Make import less verbose. [#3397]
* gpg: Add alias "Key-Grip" for parameter "Keygrip" and new
parameter "Subkey-Grip" to unattended key generation. [#3478]
* gpg: Improve "factory-reset" command for OpenPGP cards. [#3286]
* gpg: Ease switching Gnuk tokens into ECC mode by using the magic
keysize value 25519.
* gpgsm: Fix --with-colon listing in crt records for fields > 12.
* gpgsm: Do not expect X.509 keyids to be unique. [#1644]
- * agent: Fix stucked Pinentry when using --max-passphrase-days. [#3190]
+ * agent: Fix stuck Pinentry when using --max-passphrase-days. [#3190]
* agent: New option --s2k-count. [#3276 (workaround)]
* dirmngr: Do not follow https-to-http redirects. [#3436]
* dirmngr: Reduce default LDAP timeout from 100 to 15 seconds. [#3487]
* gpgconf: Ignore non-installed components for commands
--apply-profile and --apply-defaults. [#3313]
* Add configure option --enable-werror. [#2423]
See-also: gnupg-announce/2017q4/000416.html
Changes also found in 2.2.1:
* gpg: Fix formatting of the user id in batch mode key generation
if only "name-email" is given.
* gpgv: Fix annoying "not suitable for" warnings.
* wks: Convey only the newest user id to the provider. This is the
case if different names are used with the same addr-spec.
* wks: Create a complying user id for provider policy mailbox-only.
* wks: Add workaround for posteo.de.
* scd: Fix the use of large ECC keys with an OpenPGP card.
* dirmngr: Use system provided root certificates if no specific HKP
certificates are configured. If build with GNUTLS, this was
already the case.
See-also: gnupg-announce/2017q3/000415.html
Release dates of 2.2.x versions:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Version 2.2.1 (2017-09-19)
Version 2.2.2 (2017-11-07)
Version 2.2.3 (2017-11-20)
Version 2.2.4 (2017-12-20)
Version 2.2.5 (2018-02-22)
Version 2.2.6 (2018-04-09)
Version 2.2.7 (2018-05-02)
Version 2.2.8 (2018-06-08)
Version 2.2.9 (2018-07-12)
Version 2.2.10 (2018-08-30)
Version 2.2.11 (2018-11-06)
Version 2.2.12 (2018-12-14)
Version 2.2.13 (2019-02-12)
Version 2.2.14 (2019-03-19)
Version 2.2.15 (2019-03-26)
Version 2.2.16 (2019-05-28)
Version 2.2.17 (2019-07-09)
Version 2.2.18 (2019-11-25)
Version 2.2.19 (2019-12-07)
Noteworthy changes in version 2.2.0 (2017-08-28)
------------------------------------------------
This is the new long term stable branch. This branch will only see
bug fixes and no new features.
* gpg: Reverted change in 2.1.23 so that --no-auto-key-retrieve is
again the default.
* Fixed a few minor bugs.
See-also: gnupg-announce/2017q3/000413.html
Noteworthy changes in version 2.1.23 (2017-08-09)
-------------------------------------------------
* gpg: "gpg" is now installed as "gpg" and not anymore as "gpg2".
If needed, the new configure option --enable-gpg-is-gpg2 can be
used to revert this.
* gpg: Options --auto-key-retrieve and --auto-key-locate "local,wkd"
are now used by default. Note: this enables keyserver and Web Key
Directory operators to notice when a signature from a locally
non-available key is being verified for the first time or when
you intend to encrypt to a mail address without having the key
locally. This new behaviour will eventually make key discovery
much easier and mostly automatic. Disable this by adding
no-auto-key-retrieve
auto-key-locate local
to your gpg.conf.
* agent: Option --no-grab is now the default. The new option --grab
allows to revert this.
* gpg: New import option "show-only".
* gpg: New option --disable-dirmngr to entirely disable network
access for gpg.
* gpg,gpgsm: Tweaked DE-VS compliance behaviour.
* New configure flag --enable-all-tests to run more extensive tests
during "make check".
* gpgsm: The keygrip is now always printed in colon mode as
documented in the man page.
* Fixed connection timeout problem under Windows.
See-also: gnupg-announce/2017q3/000412.html
Noteworthy changes in version 2.1.22 (2017-07-28)
-------------------------------------------------
* gpg: Extend command --quick-set-expire to allow for setting the
expiration time of subkeys.
* gpg: By default try to repair keys during import. New sub-option
no-repair-keys for --import-options.
* gpg,gpgsm: Improved checking and reporting of DE-VS compliance.
* gpg: New options --key-origin and --with-key-origin. Store the
time of the last key update from keyservers, WKD, or DANE.
* agent: New option --ssh-fingerprint-digest.
* dimngr: Lower timeouts on keyserver connection attempts and made
it configurable.
* dirmngr: Tor will now automatically be detected and used. The
option --no-use-tor disables Tor detection.
* dirmngr: Now detects a changed /etc/resolv.conf.
* agent,dirmngr: Initiate shutdown on removal of the GnuPG home
directory.
* gpg: Avoid caching passphrase for failed symmetric encryption.
* agent: Support for unprotected ssh keys.
* dirmngr: Fixed name resolving on systems using only v6
nameservers.
* dirmngr: Allow the use of TLS over http proxies.
* w32: Change directory of the daemons after startup.
* wks: New man pages for client and server.
* Many other bug fixes.
See-also: gnupg-announce/2017q3/000411.html
Noteworthy changes in version 2.1.21 (2017-05-15)
-------------------------------------------------
* gpg,gpgsm: Fix corruption of old style keyring.gpg files. This
bug was introduced with version 2.1.20. Note that the default
pubring.kbx format was not affected.
* gpg,dirmngr: Removed the skeleton config file support. The
system's standard methods for providing default configuration
files should be used instead.
* w32: The Windows installer now allows installation of GnuPG
without Administrator permissions.
* gpg: Fixed import filter property match bug.
* scd: Removed Linux support for Cardman 4040 PCMCIA reader.
* scd: Fixed some corner case bugs in resume/suspend handling.
* Many minor bug fixes and code cleanup.
See-also: gnupg-announce/2017q2/000405.html
Noteworthy changes in version 2.1.20 (2017-04-03)
-------------------------------------------------
* gpg: New properties 'expired', 'revoked', and 'disabled' for the
import and export filters.
* gpg: New command --quick-set-primary-uid.
* gpg: New compliance field for the --with-colon key listing.
* gpg: Changed the key parser to generalize the processing of local
meta data packets.
* gpg: Fixed assertion failure in the TOFU trust model.
* gpg: Fixed exporting of zero length user ID packets.
* scd: Improved support for multiple readers.
* scd: Fixed timeout handling for key generation.
* agent: New option --enable-extended-key-format.
* dirmngr: Do not add a keyserver to a new dirmngr.conf. Dirmngr
uses a default keyserver.
* dimngr: Do not treat TLS warning alerts as severe error when
building with GNUTLS.
* dirmngr: Actually take /etc/hosts in account.
* wks: Fixed client problems on Windows. Published keys are now set
to world-readable.
* tests: Fixed creation of temporary directories.
* A socket directory for a non standard GNUGHOME is now created on
the fly under /run/user. Thus "gpgconf --create-socketdir" is now
optional. The use of "gpgconf --remove-socketdir" to clean up
obsolete socket directories is however recommended to avoid
cluttering /run/user with useless directories.
* Fixed build problems on some platforms.
See-also: gnupg-announce/2017q2/000404.html
Noteworthy changes in version 2.1.19 (2017-03-01)
-------------------------------------------------
* gpg: Print a warning if Tor mode is requested but the Tor daemon
is not running.
* gpg: New status code DECRYPTION_KEY to print the actual private
key used for decryption.
* gpgv: New options --log-file and --debug.
* gpg-agent: Revamp the prompts to ask for card PINs.
* scd: Support for multiple card readers.
* scd: Removed option --debug-disable-ticker. Ticker is used
only when it is required to watch removal of device/card.
* scd: Improved detection of card inserting and removal.
* dirmngr: New option --disable-ipv4.
* dirmngr: New option --no-use-tor to explicitly disable the use of
Tor.
* dirmngr: The option --allow-version-check is now required even if
the option --use-tor is also used.
* dirmngr: Handle a missing nsswitch.conf gracefully.
* dirmngr: Avoid PTR lookups for keyserver pools. The are only done
for the debug command "keyserver --hosttable".
* dirmngr: Rework the internal certificate cache to support classes
of certificates. Load system provided certificates on startup.
Add options --tls, --no-crl, and --systrust to the "VALIDATE"
command.
* dirmngr: Add support for the ntbtls library.
* wks: Create mails with a "WKS-Phase" header. Fix detection of
Draft-2 mode.
* The Windows installer is now build with limited TLS support.
* Many other bug fixes and new regression tests.
See-also: gnupg-announce/2017q1/000402.html
Noteworthy changes in version 2.1.18 (2017-01-23)
-------------------------------------------------
* gpg: Remove bogus subkey signature while cleaning a key (with
export-clean, import-clean, or --edit-key's sub-command clean)
* gpg: Allow freezing the clock with --faked-system-time.
* gpg: New --export-option flag "backup", new --import-option flag
"restore".
* gpg-agent: Fixed long delay due to a regression in the progress
callback code.
* scd: Lots of code cleanup and internal changes.
* scd: Improved the internal CCID driver.
* dirmngr: Fixed problem with the DNS glue code (removal of the
trailing dot in domain names).
* dirmngr: Make sure that Tor is actually enabled after changing the
conf file and sending SIGHUP or "gpgconf --reload dirmngr".
* dirmngr: Fixed Tor access to IPv6 addresses. Note that current
versions of Tor may require that the flag "IPv6Traffic" is used
with the option "SocksPort" in torrc to actually allow IPv6
traffic.
* dirmngr: Fixed HKP for literally given IPv6 addresses.
* dirmngr: Enabled reverse DNS lookups via Tor.
* dirmngr: Added experimental SRV record lookup for WKD.
See commit 88dc3af3d4ae1afe1d5e136bc4c38bc4e7d4cd10 for details.
* dirmngr: For HKP use "pgpkey-hkps" and "pgpkey-hkp" in SRV record
lookups. Avoid SRV record lookup when a port is explicitly
specified. This fixes a regression from the 1.4 and 2.0 behavior.
* dirmngr: Gracefully handle a missing /etc/nsswitch.conf. Ignore
negation terms (e.g. "[!UNAVAIL=return]" instead of bailing out.
* dirmngr: Better debug output for flags "dns" and "network".
* dirmngr: On reload mark all known HKP servers alive.
* gpgconf: Allow keyword "all" for --launch, --kill, and --reload.
* tools: gpg-wks-client now ignores a missing policy file on the
server.
* Avoid unnecessary ambiguity error message in the option parsing.
* Further improvements of the regression test suite.
* Fixed building with --disable-libdns configure option.
* Fixed a crash running the tests on 32 bit architectures.
* Fixed spurious failures on BSD system in the spawn functions.
This affected for example gpg-wks-client and gpgconf.
See-also: gnupg-announce/2017q1/000401.html
Noteworthy changes in version 2.1.17 (2016-12-20)
-------------------------------------------------
* gpg: By default new keys expire after 2 years.
* gpg: New command --quick-set-expire to conveniently change the
expiration date of keys.
* gpg: Option and command names have been changed for easier
comprehension. The old names are still available as aliases.
* gpg: Improved the TOFU trust model.
* gpg: New option --default-new-key-algo.
* scd: Support OpenPGP card V3 for RSA.
* dirmngr: Support for the ADNS library has been removed. Instead
William Ahern's Libdns is now source included and used on all
platforms. This enables Tor support on all platforms. The new
option --standard-resolver can be used to disable this code at
runtime. In case of build problems the new configure option
--disable-libdns can be used to build without Libdns.
* dirmngr: Lazily launch ldap reaper thread.
* tools: New options --check and --status-fd for gpg-wks-client.
* The UTF-8 byte order mark is now skipped when reading conf files.
* Fixed many bugs and regressions.
* Major improvements to the test suite. For example it is possible
to run the external test suite of GPGME.
See-also: gnupg-announce/2016q4/000400.html
Noteworthy changes in version 2.1.16 (2016-11-18)
-------------------------------------------------
* gpg: New algorithm for selecting the best ranked public key when
using a mail address with -r, -R, or --locate-key.
* gpg: New option --with-tofu-info to print a new "tfs" record in
colon formatted key listings.
* gpg: New option --compliance as an alternative way to specify
options like --rfc2440, --rfc4880, et al.
* gpg: Many changes to the TOFU implementation.
* gpg: Improve usability of --quick-gen-key.
* gpg: In --verbose mode print a diagnostic when a pinentry is
launched.
* gpg: Remove code which warns for old versions of gnome-keyring.
* gpg: New option --override-session-key-fd.
* gpg: Option --output does now work with --verify.
* gpgv: New option --output to allow saving the verified data.
* gpgv: New option --enable-special-filenames.
* agent, dirmngr: New --supervised mode for use by systemd and alike.
* agent: By default listen on all available sockets using standard
names.
* agent: Invoke scdaemon with --homedir.
* dirmngr: On Linux now detects the removal of its own socket and
terminates.
* scd: Support ECC key generation.
* scd: Support more card readers.
* dirmngr: New option --allow-version-check to download a software
version database in the background.
* dirmngr: Use system provided CAs if no --hkp-cacert is given.
* dirmngr: Use a default keyserver if none is explicitly set
* gpgconf: New command --query-swdb to check software versions
against an copy of an online database.
* gpgconf: Print the socket directory with --list-dirs.
* tools: The WKS tools now support draft version -02.
* tools: Always build gpg-wks-client and install under libexec.
* tools: New option --supported for gpg-wks-client.
* The log-file option now accepts a value "socket://" to log to the
socket named "S.log" in the standard socket directory.
* Provide fake pinentries for use by tests cases of downstream
developers.
* Fixed many bugs and regressions.
* Many changes and improvements for the test suite.
See-also: gnupg-announce/2016q4/000398.html
Noteworthy changes in version 2.1.15 (2016-08-18)
-------------------------------------------------
* gpg: Remove the --tofu-db-format option and support for the split
TOFU database.
* gpg: Add option --sender to prepare for coming features.
* gpg: Add option --input-size-hint to help progress indicators.
* gpg: Extend the PROGRESS status line with the counted unit.
* gpg: Avoid publishing the GnuPG version by default with --armor.
* gpg: Properly ignore legacy keys in the keyring cache.
* gpg: Always print fingerprint records in --with-colons mode.
* gpg: Make sure that keygrips are printed for each subkey in
--with-colons mode.
* gpg: New import filter "drop-sig".
* gpgsm: Fix a bug in the machine-readable key listing.
* gpg,gpgsm: Block signals during keyring updates to limits the
effects of a Ctrl-C at the wrong time.
* g13: Add command --umount and other fixes for dm-crypt.
* agent: Fix regression in SIGTERM handling.
* agent: Cleanup of the ssh-agent code.
* agent: Allow import of overly long keys.
* scd: Fix problems with card removal.
* dirmngr: Remove all code for running as a system service.
* tools: Make gpg-wks-client conforming to the specs.
* tests: Improve the output of the new regression test tool.
* tests: Distribute the standalone test runner.
* tests: Run each test in a clean environment.
* Spelling and grammar fixes.
See-also: gnupg-announce/2016q3/000396.html
Noteworthy changes in version 2.1.14 (2016-07-14)
-------------------------------------------------
* gpg: Removed options --print-dane-records and --print-pka-records.
The new export options "export-pka" and "export-dane" can instead
be used with the export command.
* gpg: New options --import-filter and --export-filter.
* gpg: New import options "import-show" and "import-export".
* gpg: New option --no-keyring.
* gpg: New command --quick-revuid.
* gpg: New options -f/--recipient-file and -F/--hidden-recipient-file
to directly specify encryption keys.
* gpg: New option --mimemode to indicate that the content is a MIME
part. Does only enable --textmode right now.
* gpg: New option --rfc4880bis to allow experiments with proposed
changes to the current OpenPGP specs.
* gpg: Fix regression in the "fetch" sub-command of --card-edit.
* gpg: Fix regression since 2.1 in option --try-all-secrets.
* gpgv: Change default options for extra security.
* gpgsm: No more root certificates are installed by default.
* agent: "updatestartuptty" does now affect more environment
variables.
* scd: The option --homedir does now work with scdaemon.
* scd: Support some more GEMPlus card readers.
* gpgtar: Fix handling of '-' as file name.
* gpgtar: New commands --create and --extract.
* gpgconf: Tweak for --list-dirs to better support shell scripts.
* tools: Add programs gpg-wks-client and gpg-wks-server to implement
a Web Key Service. The configure option --enable-wks-tools is
required to build them; they should be considered Beta software.
* tests: Complete rework of the openpgp part of the test suite. The
test scripts have been changed from Bourne shell scripts to Scheme
programs. A customized scheme interpreter (gpgscm) is included.
This change was triggered by the need to run the test suite on
non-Unix platforms.
* The rendering of the man pages has been improved.
See-also: gnupg-announce/2016q3/000393.html
Noteworthy changes in version 2.1.13 (2016-06-16)
-------------------------------------------------
* gpg: New command --quick-addkey. Extend the --quick-gen-key
command.
* gpg: New --keyid-format "none" which is now also the default.
* gpg: New option --with-subkey-fingerprint.
* gpg: Include Signer's UID subpacket in signatures if the secret key
has been specified using a mail address and the new option
--disable-signer-uid is not used.
* gpg: Allow unattended deletion of a secret key.
* gpg: Allow export of non-passphrase protected secret keys.
* gpg: New status lines KEY_CONSIDERED and NOTATION_FLAGS.
* gpg: Change status line TOFU_STATS_LONG to use '~' as
a non-breaking-space character.
* gpg: Speedup key listings in Tofu mode.
* gpg: Make sure that the current and total values of a PROGRESS
status line are small enough.
* gpgsm: Allow the use of AES192 and SERPENT ciphers.
* dirmngr: Adjust WKD lookup to current specs.
* dirmngr: Fallback to LDAP v3 if v2 is is not supported.
* gpgconf: New commands --create-socketdir and --remove-socketdir,
new option --homedir.
* If a /run/user/$UID directory exists, that directory is now used
for IPC sockets instead of the GNUPGHOME directory. This fixes
problems with NFS and too long socket names and thus avoids the
need for redirection files.
* The Speedo build systems now uses the new versions.gnupg.org server
to retrieve the default package versions.
* Fix detection of libusb on FreeBSD.
* Speedup fd closing after a fork.
See-also: gnupg-announce/2016q2/000390.html
Noteworthy changes in version 2.1.12 (2016-05-04)
-------------------------------------------------
* gpg: New --edit-key sub-command "change-usage" for testing
purposes.
* gpg: Out of order key-signatures are now systematically detected
and fixed by --edit-key.
* gpg: Improved detection of non-armored messages.
* gpg: Removed the extra prompt needed to create Curve25519 keys.
* gpg: Improved user ID selection for --quick-sign-key.
* gpg: Use the root CAs provided by the system with --fetch-key.
* gpg: Add support for the experimental Web Key Directory key
location service.
* gpg: Improve formatting of Tofu messages and emit new Tofu specific
status lines.
* gpgsm: Add option --pinentry-mode to support a loopback pinentry.
* gpgsm: A new pubring.kbx is now created with the header blob so
that gpg can detect that the keybox format needs to be used.
* agent: Add read support for the new private key protection format
openpgp-s2k-ocb-aes.
* agent: Add read support for the new extended private key format.
* agent: Default to --allow-loopback-pinentry and add option
--no-allow-loopback-pinentry.
* scd: Changed to use the new libusb 1.0 API for the internal CCID
driver.
* dirmngr: The dirmngr-client does now auto-detect the PEM format.
* g13: Add experimental support for dm-crypt.
* w32: Tofu support is now available with the Speedo build method.
* w32: Removed the need for libiconv.dll.
* The man pages for gpg and gpgv are now installed under the correct
name (gpg2 or gpg - depending on a configure option).
* Lots of internal cleanups and bug fixes.
See-also: gnupg-announce/2016q2/000387.html
Noteworthy changes in version 2.1.11 (2016-01-26)
-------------------------------------------------
* gpg: New command --export-ssh-key to replace the gpgkey2ssh tool.
* gpg: Allow to generate mail address only keys with --gen-key.
* gpg: "--list-options show-usage" is now the default.
* gpg: Make lookup of DNS CERT records holding an URL work.
* gpg: Emit PROGRESS status lines during key generation.
* gpg: Don't check for ambiguous or non-matching key specification in
the config file or given to --encrypt-to. This feature will return
in 2.3.x.
* gpg: Lock keybox files while updating them.
* gpg: Solve rare error on Windows during keyring and Keybox updates.
* gpg: Fix possible keyring corruption. (bug#2193)
* gpg: Fix regression of "bkuptocard" sub-command in --edit-key and
remove "checkbkupkey" sub-command introduced with 2.1. (bug#2169)
* gpg: Fix internal error in gpgv when using default keyid-format.
* gpg: Fix --auto-key-retrieve to work with dirmngr.conf configured
keyservers. (bug#2147).
* agent: New option --pinentry-timeout.
* scd: Improve unplugging of USB readers under Windows.
* scd: Fix regression for generating RSA keys on card.
* dirmmgr: All configured keyservers are now searched.
* dirmngr: Install CA certificate for hkps.pool.sks-keyservers.net.
Use this certificate even if --hkp-cacert is not used.
* gpgtar: Add actual encryption code. gpgtar does now fully replace
gpg-zip.
* gpgtar: Fix filename encoding problem on Windows.
* Print a warning if a GnuPG component is using an older version of
gpg-agent, dirmngr, or scdaemon.
See-also: gnupg-announce/2016q1/000383.html
Noteworthy changes in version 2.1.10 (2015-12-04)
-------------------------------------------------
* gpg: New trust models "tofu" and "tofu+pgp".
* gpg: New command --tofu-policy. New options --tofu-default-policy
and --tofu-db-format.
* gpg: New option --weak-digest to specify hash algorithms which
should be considered weak.
* gpg: Allow the use of multiple --default-key options; take the last
available key.
* gpg: New option --encrypt-to-default-key.
* gpg: New option --unwrap to only strip the encryption layer.
* gpg: New option --only-sign-text-ids to exclude photo IDs from key
signing.
* gpg: Check for ambiguous or non-matching key specification in the
config file or given to --encrypt-to.
* gpg: Show the used card reader with --card-status.
* gpg: Print export statistics and an EXPORTED status line.
* gpg: Allow selecting subkeys by keyid in --edit-key.
* gpg: Allow updating the expiration time of multiple subkeys at
once.
* dirmngr: New option --use-tor. For full support this requires
libassuan version 2.4.2 and a patched version of libadns
(e.g. adns-1.4-g10-7 as used by the standard Windows installer).
* dirmngr: New option --nameserver to specify the nameserver used in
Tor mode.
* dirmngr: Keyservers may again be specified by IP address.
* dirmngr: Fixed problems in resolving keyserver pools.
* dirmngr: Fixed handling of premature termination of TLS streams so
that large numbers of keys can be refreshed via hkps.
* gpg: Fixed a regression in --locate-key [since 2.1.9].
* gpg: Fixed another bug for keyrings with legacy keys.
* gpgsm: Allow combinations of usage flags in --gen-key.
* Make tilde expansion work with most options.
* Many other cleanups and bug fixes.
See-also: gnupg-announce/2015q4/000381.html
Noteworthy changes in version 2.1.9 (2015-10-09)
------------------------------------------------
* gpg: Allow fetching keys via OpenPGP DANE (--auto-key-locate). New
option --print-dane-records. [Update: --print-dane-records replaced
in 2.1.4.]
* gpg: Fix for a problem with PGP-2 keys in a keyring.
* gpg: Fail with an error instead of a warning if a modern cipher
algorithm is used without a MDC.
* agent: New option --pinentry-invisible-char.
* agent: Always do a RSA signature verification after creation.
* agent: Fix a regression in ssh-add-ing Ed25519 keys.
* agent: Fix ssh fingerprint computation for nistp384 and EdDSA.
* agent: Fix crash during passphrase entry on some platforms.
* scd: Change timeout to fix problems with some 2.1 cards.
* dirmngr: Displayed name is now Key Acquirer.
* dirmngr: Add option --keyserver. Deprecate that option for gpg.
Install a dirmngr.conf file from a skeleton for new installations.
See-also: gnupg-announce/2015q4/000380.html
Noteworthy changes in version 2.1.8 (2015-09-10)
------------------------------------------------
* gpg: Sending very large keys to the keyservers works again.
* gpg: Validity strings in key listings are now again translatable.
* gpg: Emit FAILURE status lines to help GPGME.
* gpg: Does not anymore link to Libksba to reduce dependencies.
* gpgsm: Export of secret keys via Assuan is now possible.
* agent: Raise the maximum passphrase length from 100 to 255 bytes.
* agent: Fix regression using EdDSA keys with ssh.
* Does not anymore use a build timestamp by default.
* The fallback encoding for broken locale settings changed
from Latin-1 to UTF-8.
* Many code cleanups and improved internal documentation.
* Various minor bug fixes.
See-also: gnupg-announce/2015q3/000379.html
Noteworthy changes in version 2.1.7 (2015-08-11)
------------------------------------------------
* gpg: Support encryption with Curve25519 if Libgcrypt 1.7 is used.
* gpg: In the --edit-key menu: Removed the need for "toggle", changed
how secret keys are indicated, new commands "fpr *" and "grip".
* gpg: More fixes related to legacy keys in a keyring.
* gpgv: Does now also work with a "trustedkeys.kbx" file.
* scd: Support some feature from the OpenPGP card 3.0 specs.
* scd: Improved ECC support
* agent: New option --force for the DELETE_KEY command.
* w32: Look for the Pinentry at more places.
* Dropped deprecated gpgsm-gencert.sh
* Various other bug fixes.
See-also: gnupg-announce/2015q3/000371.html
Noteworthy changes in version 2.1.6 (2015-07-01)
------------------------------------------------
* agent: New option --verify for the PASSWD command.
* gpgsm: Add command option "offline" as an alternative to
--disable-dirmngr.
* gpg: Do not prompt multiple times for a password in pinentry
loopback mode.
* Allow the use of debug category names with --debug.
* Using gpg-agent and gpg/gpgsm with different locales will now show
the correct translations in Pinentry.
* gpg: Improve speed of --list-sigs and --check-sigs.
* gpg: Make --list-options show-sig-subpackets work again.
* gpg: Fix an export problem for old keyrings with PGP-2 keys.
* scd: Support PIN-pads on more readers.
* dirmngr: Properly cleanup zombie LDAP helper processes and avoid
hangs on dirmngr shutdown.
* Various other bug fixes.
See-also: gnupg-announce/2015q3/000370.html
Noteworthy changes in version 2.1.5 (2015-06-11)
------------------------------------------------
* Support for an external passphrase cache.
* Support for the forthcoming version 3 OpenPGP smartcard.
* Manuals now show the actual used file names.
* Prepared for improved integration with Emacs.
* Code cleanups and minor bug fixes.
See-also: gnupg-announce/2015q2/000369.html
Noteworthy changes in version 2.1.4 (2015-05-12)
------------------------------------------------
* gpg: Add command --quick-adduid to non-interactively add a new user
id to an existing key.
* gpg: Do no enable honor-keyserver-url by default. Make it work if
enabled.
* gpg: Display the serial number in the --card-status output again.
* agent: Support for external password managers.
Add option --no-allow-external-cache.
* scdaemon: Improved handling of extended APDUs.
* Make HTTP proxies work again.
* All network access including DNS as been moved to Dirmngr.
* Allow building without LDAP support.
* Fixed lots of smaller bugs.
See-also: gnupg-announce/2015q2/000366.html
Noteworthy changes in version 2.1.3 (2015-04-11)
------------------------------------------------
* gpg: LDAP keyservers are now supported by 2.1.
* gpg: New option --with-icao-spelling.
* gpg: New option --print-pka-records. Changed the PKA method to use
CERT records and hashed names. [Update: --print-pka-records
replaced in 2.1.14.]
* gpg: New command --list-gcrypt-config. New parameter "curve"
for --list-config.
* gpg: Print a NEWSIG status line like gpgsm always did.
* gpg: Print MPI values with --list-packets and --verbose.
* gpg: Write correct MPI lengths with ECC keys.
* gpg: Skip legacy PGP-2 keys while searching.
* gpg: Improved searching for mail addresses when using a keybox.
* gpgsm: Changed default algos to AES-128 and SHA-256.
* gpgtar: Fixed extracting files with sizes of a multiple of 512.
* dirmngr: Fixed SNI handling for hkps pools.
* dirmngr: extra-certs and trusted-certs are now always loaded from
the sysconfig dir instead of the homedir.
* Fixed possible problems due to compiler optimization, two minor
regressions, and other bugs.
See-also: gnupg-announce/2015q2/000365.html
Noteworthy changes in version 2.1.2 (2015-02-11)
------------------------------------------------
* gpg: The parameter 'Passphrase' for batch key generation works
again.
* gpg: Using a passphrase option in batch mode now has the expected
effect on --quick-gen-key.
* gpg: Improved reporting of unsupported PGP-2 keys.
* gpg: Added support for algo names when generating keys using
--command-fd.
* gpg: Fixed DoS based on bogus and overlong key packets.
* agent: When setting --default-cache-ttl the value
for --max-cache-ttl is adjusted to be not lower than the former.
* agent: Fixed problems with the new --extra-socket.
* agent: Made --allow-loopback-pinentry changeable with gpgconf.
* agent: Fixed importing of unprotected openpgp keys.
* agent: Now tries to use a fallback pinentry if the standard
pinentry is not installed.
* scd: Added support for ECDH.
* Fixed several bugs related to bogus keyrings and improved some
other code.
See-also: gnupg-announce/2015q1/000361.html
Noteworthy changes in version 2.1.1 (2014-12-16)
------------------------------------------------
* gpg: Detect faulty use of --verify on detached signatures.
* gpg: New import option "keep-ownertrust".
* gpg: New sub-command "factory-reset" for --card-edit.
* gpg: A stub key for smartcards is now created by --card-status.
* gpg: Fixed regression in --refresh-keys.
* gpg: Fixed regression in %g and %p codes for --sig-notation.
* gpg: Fixed best matching hash algo detection for ECDSA and EdDSA.
* gpg: Improved perceived speed of secret key listisngs.
* gpg: Print number of skipped PGP-2 keys on import.
* gpg: Removed the option aliases --throw-keyid and --notation-data;
use --throw-keyids and --set-notation instead.
* gpg: New import option "keep-ownertrust".
* gpg: Skip too large keys during import.
* gpg,gpgsm: New option --no-autostart to avoid starting gpg-agent or
dirmngr.
* gpg-agent: New option --extra-socket to provide a restricted
command set for use with remote clients.
* gpgconf --kill does not anymore start a service only to kill it.
* gpg-pconnect-agent: Add convenience option --uiserver.
* Fixed keyserver access for Windows.
* Fixed build problems on Mac OS X
* The Windows installer does now install development files
* More translations (but most of them are not complete).
* To support remotely mounted home directories, the IPC sockets may
now be redirected. This feature requires Libassuan 2.2.0.
* Improved portability and the usual bunch of bug fixes.
See-also: gnupg-announce/2014q4/000360.html
Noteworthy changes in version 2.1.0 (2014-11-06)
------------------------------------------------
This release introduces a lot of changes. Most of them are internal
and thus not user visible. However, some long standing behavior has
slightly changed and it is strongly suggested that an existing
"~/.gnupg" directory is backed up before this version is used.
A verbose description of the major new features and changes can be
found in the file doc/whats-new-in-2.1.txt.
* gpg: All support for v3 (PGP 2) keys has been dropped. All
signatures are now created as v4 signatures. v3 keys will be
removed from the keyring.
* gpg: With pinentry-0.9.0 the passphrase "enter again" prompt shows
up in the same window as the "new passphrase" prompt.
* gpg: Allow importing keys with duplicated long key ids.
* dirmngr: May now be build without support for LDAP.
* For a complete list of changes see the lists of changes for the
2.1.0 beta versions below. Note that all relevant fixes from
versions 2.0.14 to 2.0.26 are also applied to this version.
[Noteworthy changes in version 2.1.0-beta864 (2014-10-03)]
* gpg: Removed the GPG_AGENT_INFO related code. GnuPG does now
always use a fixed socket name in its home directory.
* gpg: Renamed --gen-key to --full-gen-key and re-added a --gen-key
command with less choices.
* gpg: Use SHA-256 for all signature types also on RSA keys.
* gpg: Default keyring is now created with a .kbx suffix.
* gpg: Add a shortcut to the key capabilities menu (e.g. "=e" sets the
encryption capabilities).
* gpg: Fixed obsolete options parsing.
* Further improvements for the alternative speedo build system.
[Noteworthy changes in version 2.1.0-beta834 (2014-09-18)]
* gpg: Improved passphrase caching.
* gpg: Switched to algorithm number 22 for EdDSA.
* gpg: Removed CAST5 from the default preferences.
* gpg: Order SHA-1 last in the hash preferences.
* gpg: Changed default cipher for --symmetric to AES-128.
* gpg: Fixed export of ECC keys and import of EdDSA keys.
* dirmngr: Fixed the KS_FETCH command.
* The speedo build system now downloads related packages and works
for non-Windows platforms.
[Noteworthy changes in version 2.1.0-beta783 (2014-08-14)]
* gpg: Add command --quick-gen-key.
* gpg: Make --quick-sign-key promote local key signatures.
* gpg: Added "show-usage" sub-option to --list-options.
* gpg: Screen keyserver responses to avoid importing unwanted keys
from rogue servers.
* gpg: Removed the option --pgp2 and --rfc1991 and the ability to
create PGP-2 compatible messages.
* gpg: Removed options --compress-keys and --compress-sigs.
* gpg: Cap attribute packets at 16MB.
* gpg: Improved output of --list-packets.
* gpg: Make with-colons output of --search-keys work again.
* gpgsm: Auto-create the ".gnupg" directory like gpg does.
* agent: Fold new passphrase warning prompts into one.
* scdaemon: Add support for the Smartcard-HSM card.
* scdaemon: Remove the use of the pcsc-wrapper.
[Noteworthy changes in version 2.1.0-beta751 (2014-07-03)]
* gpg: Create revocation certificates during key generation.
* gpg: Create exported secret keys and revocation certifciates with
mode 0700
* gpg: The validity of user ids is now shown by default. To revert
this add "list-options no-show-uid-validity" to gpg.conf.
* gpg: Make export of secret keys work again.
* gpg: The output of --list-packets does now print the offset of the
packet and information about the packet header.
* gpg: Avoid DoS due to garbled compressed data packets. [CVE-2014-4617]
* gpg: Print more specific reason codes with the INV_RECP status.
* gpg: Cap RSA and Elgamal keysize at 4096 bit also for unattended
key generation.
* scdaemon: Support reader Gemalto IDBridge CT30 and pinpad of SCT
cyberJack go.
* The speedo build system has been improved. It is now also possible
to build a partly working installer for Windows.
[Noteworthy changes in version 2.1.0-beta442 (2014-06-05)]
* gpg: Changed the format of key listings. To revert to the old
format the option --legacy-list-mode is available.
* gpg: Add experimental signature support using curve Ed25519 and
with a patched Libgcrypt also encryption support with Curve25519.
[Update: this encryption support has been removed from 2.1.0 until
we have agreed on a suitable format.]
* gpg: Allow use of Brainpool curves.
* gpg: Accepts a space separated fingerprint as user ID. This
allows to copy and paste the fingerprint from the key listing.
* gpg: The hash algorithm is now printed for signature records in key
listings.
* gpg: Reject signatures made using the MD5 hash algorithm unless the
new option --allow-weak-digest-algos or --pgp2 are given.
* gpg: Print a warning if the Gnome-Keyring-Daemon intercepts the
communication with the gpg-agent.
* gpg: New option --pinentry-mode.
* gpg: Fixed decryption using an OpenPGP card.
* gpg: Fixed bug with deeply nested compressed packets.
* gpg: Only the major version number is by default included in the
armored output.
* gpg: Do not create a trustdb file if --trust-model=always is used.
* gpg: Protect against rogue keyservers sending secret keys.
* gpg: The format of the fallback key listing ("gpg KEYFILE") is now
more aligned to the regular key listing ("gpg -k").
* gpg: The option--show-session-key prints its output now before the
decryption of the bulk message starts.
* gpg: New %U expando for the photo viewer.
* gpg,gpgsm: New option --with-secret.
* gpgsm: By default the users are now asked via the Pinentry whether
they trust an X.509 root key. To prohibit interactive marking of
such keys, the new option --no-allow-mark-trusted may be used.
* gpgsm: New commands to export a secret RSA key in PKCS#1 or PKCS#8
format.
* gpgsm: Improved handling of re-issued CA certificates.
* agent: The included ssh agent does now support ECDSA keys.
* agent: New option --enable-putty-support to allow gpg-agent on
Windows to act as a Pageant replacement with full smartcard support.
* scdaemon: New option --enable-pinpad-varlen.
* scdaemon: Various fixes for pinpad equipped card readers.
* scdaemon: Rename option --disable-pinpad (was --disable-keypad).
- * scdaemon: Better support fo CCID readers. Now, internal CCID
+ * scdaemon: Better support for CCID readers. Now, internal CCID
driver supports readers with no auto configuration feature.
* dirmngr: Removed support for the original HKP keyserver which is
not anymore used by any site.
* dirmngr: Improved support for keyserver pools.
* tools: New option --dirmngr for gpg-connect-agent.
* The GNU Pth library has been replaced by the new nPth library.
* Support installation as portable application under Windows.
* All kind of other improvements - see the git log.
[Noteworthy changes in version 2.1.0beta3 (2011-12-20)]
* gpg: Fixed regression in the secret key export function.
* gpg: Allow generation of card keys up to 4096 bit.
* gpgsm: Preliminary support for the validation model "steed".
* gpgsm: Improved certificate creation.
* agent: Support the SSH confirm flag.
* agent: New option to select a passphrase mode. The loopback
mode may be used to bypass Pinentry.
* agent: The Assuan commands KILLAGENT and KILLSCD are working again.
* scdaemon: Does not anymore block after changing a card (regression
fix).
* tools: gpg-connect-agent does now properly display the help output
for "SCD HELP" commands.
[Noteworthy changes in version 2.1.0beta2 (2011-03-08)]
* gpg: ECC support as described by draft-jivsov-openpgp-ecc-06.txt
[Update: now known as RFC-6637].
* gpg: Print "AES128" instead of "AES". This change introduces a
little incompatibility for tools using "gpg --list-config". We
hope that these tools are written robust enough to accept this new
algorithm name as well.
* gpgsm: New feature to create certificates from a parameter file.
Add prompt to the --gen-key UI to create self-signed certificates.
* agent: TMPDIR is now also honored when creating a socket using
the --no-standard-socket option and with symcryptrun's temp files.
* scdaemon: Fixed a bug where scdaemon sends a signal to gpg-agent
running in non-daemon mode.
* dirmngr: Fixed CRL loading under W32 (bug#1010).
* Dirmngr has taken over the function of the keyserver helpers. Thus
we now have a specified direct interface to keyservers via Dirmngr.
LDAP, DNS and mail backends are not yet implemented.
* Fixed TTY management for pinentries and session variable update
problem.
[Noteworthy changes in version 2.1.0beta1 (2010-10-26)]
* gpg: secring.gpg is not anymore used but all secret key operations
are delegated to gpg-agent. The import command moves secret keys
to the agent.
* gpg: The OpenPGP import command is now able to merge secret keys.
* gpg: Encrypted OpenPGP messages with trailing data (e.g. other
OpenPGP packets) are now correctly parsed.
* gpg: Given sufficient permissions Dirmngr is started automagically.
* gpg: Fixed output of "gpgconf --check-options".
* gpg: Removed options --export-options(export-secret-subkey-passwd)
and --simple-sk-checksum.
* gpg: New options --try-secret-key.
* gpg: Support DNS lookups for SRV, PKA and CERT on W32.
* gpgsm: The --audit-log feature is now more complete.
* gpgsm: The default for --include-cert is now to include all
certificates in the chain except for the root certificate.
* gpgsm: New option --ignore-cert-extension.
* g13: The G13 tool for disk encryption key management has been
added.
* agent: If the agent's --use-standard-socket option is active, all
tools try to start and daemonize the agent on the fly. In the past
this was only supported on W32; on non-W32 systems the new
configure option --disable-standard-socket may now be used to
disable this new default.
* agent: New and changed passphrases are now created with an
iteration count requiring about 100ms of CPU work.
* dirmngr: Dirmngr is now a part of this package. It is now also
expected to run as a system service and the configuration
directories are changed to the GnuPG name space. [Update: 2.1.0
starts dirmngr on demand as user daemon.]
* Support for Windows CE. [Update: This has not been tested for the
2.1.0 release]
* Numerical values may now be used as an alternative to the
debug-level keywords.
See-also: gnupg-announce/2014q4/000358.html
Version 2.0.28 (2015-06-02)
Version 2.0.27 (2015-02-18)
Version 2.0.26 (2014-08-12)
Version 2.0.25 (2014-06-30)
Version 2.0.24 (2014-06-24)
Version 2.0.23 (2014-06-03)
Version 2.0.22 (2013-10-04)
Version 2.0.21 (2013-08-19)
Version 2.0.20 (2013-05-10)
Version 2.0.19 (2012-03-27)
Version 2.0.18 (2011-08-04)
Version 2.0.17 (2011-01-13)
Version 2.0.16 (2010-07-19)
Version 2.0.15 (2010-03-09)
Version 2.0.14 (2009-12-21)
Noteworthy changes in version 2.0.13 (2009-09-04)
-------------------------------------------------
* GPG now generates 2048 bit RSA keys by default. The default hash
algorithm preferences has changed to prefer SHA-256 over SHA-1.
2048 bit DSA keys are now generated to use a 256 bit hash algorithm
* The envvars XMODIFIERS, GTK_IM_MODULE and QT_IM_MODULE are now
passed to the Pinentry to make SCIM work.
* The GPGSM command --gen-key features a --batch mode and implements
all features of gpgsm-gencert.sh in standard mode.
* New option --re-import for GPGSM's IMPORT server command.
* Enhanced writing of existing keys to OpenPGP v2 cards.
* Add hack to the internal CCID driver to allow the use of some
Omnikey based card readers with 2048 bit keys.
* GPG now repeatedly asks the user to insert the requested OpenPGP
card. This can be disabled with --limit-card-insert-tries=1.
* Minor bug fixes.
See-also: gnupg-announce/2009q3/000294.html
Noteworthy changes in version 2.0.12 (2009-06-17)
-------------------------------------------------
* GPGSM now always lists ephemeral certificates if specified by
fingerprint or keygrip.
* New command "KEYINFO" for GPG_AGENT. GPGSM now also returns
information about smartcards.
* Made sure not to leak file descriptors if running gpg-agent with a
command. Restore the signal mask to solve a problem in Mono.
* Changed order of the confirmation questions for root certificates
and store negative answers in trustlist.txt.
* Better synchronization of concurrent smartcard sessions.
* Support 2048 bit OpenPGP cards.
* Support Telesec Netkey 3 cards.
* The gpg-protect-tool now uses gpg-agent via libassuan. Under
Windows the Pinentry will now be put into the foreground.
* Changed code to avoid a possible Mac OS X system freeze.
See-also: gnupg-announce/2009q2/000288.html
Noteworthy changes in version 2.0.11 (2009-03-03)
-------------------------------------------------
* Fixed a problem in SCDAEMON which caused unexpected card resets.
* SCDAEMON is now aware of the Geldkarte.
* The SCDAEMON option --allow-admin is now used by default.
* GPGCONF now restarts SCdaemon if necessary.
* The default cipher algorithm in GPGSM is now again 3DES. This is
due to interoperability problems with Outlook 2003 which still
can't cope with AES.
See-also: gnupg-announce/2009q1/000287.html
Noteworthy changes in version 2.0.10 (2009-01-12)
-------------------------------------------------
* [gpg] New keyserver helper gpg2keys_kdns as generic DNS CERT
lookup. Run with --help for a short description. Requires the
ADNS library.
* [gpg] New mechanisms "local" and "nodefault" for --auto-key-locate.
Fixed a few problems with this option.
* [gpg] New command --locate-keys.
* [gpg] New options --with-sig-list and --with-sig-check.
* [gpg] The option "-sat" is no longer an alias for --clearsign.
* [gpg] The option --fixed-list-mode is now implicitly used and obsolete.
* [gpg] New control statement %ask-passphrase for the unattended key
generation.
* [gpg] The algorithm to compute the SIG_ID status has been changed.
* [gpgsm] Now uses AES by default.
* [gpgsm] Made --output option work with --export-secret-key-p12.
* [gpg-agent] Terminate process if the own listening socket is not
anymore served by ourself.
* [scdaemon] Made it more robust on W32.
* [gpg-connect-agent] Accept commands given as command line arguments.
* [w32] Initialized the socket subsystem for all keyserver helpers.
* [w32] The sysconf directory has been moved from a subdirectory of
the installation directory to %CSIDL_COMMON_APPDATA%/GNU/etc/gnupg.
* [w32] The gnupg2.nls directory is not anymore used. The standard
locale directory is now used.
* [w32] Fixed a race condition between gpg and gpgsm in the use of
temporary file names.
* The gpg-preset-passphrase mechanism works again. An arbitrary
string may now be used for a custom cache ID.
* Admin PINs are cached again (bug in 2.0.9).
* Support for version 2 OpenPGP cards.
* Libgcrypt 1.4 is now required.
See-also: gnupg-announce/2009q1/000284.html
Noteworthy changes in version 2.0.9 (2008-03-26)
------------------------------------------------
* Gpgsm always tries to locate missing certificates from a running
Dirmngr's cache.
* Tweaks for Windows.
* The Admin PIN for OpenPGP cards may now be entered with the pinpad.
* Improved certificate chain construction.
* Extended the PKITS framework.
* Fixed a bug in the ambiguous name detection.
* Fixed possible memory corruption while importing OpenPGP keys (bug
introduced with 2.0.8). [CVE-2008-1530]
* Minor bug fixes.
Noteworthy changes in version 2.0.8 (2007-12-20)
------------------------------------------------
* Enhanced gpg-connect-agent with a small scripting language.
* New option --list-config for gpgconf.
* Fixed a crash in gpgconf.
* Gpg-agent now supports the passphrase quality bar of the latest
Pinentry.
* The envvars XAUTHORITY and PINENTRY_USER_DATA are now passed to the
Pinentry.
* Fixed the auto creation of the key stub for smartcards.
* Fixed a rare bug in decryption using the OpenPGP card.
* Creating DSA2 keys is now possible.
* New option --extra-digest-algo for gpgsm to allow verification of
broken signatures.
* Allow encryption with legacy Elgamal sign+encrypt keys with option
--rfc2440.
* Windows is now a supported platform.
* Made sure that under Windows the file permissions of the socket are
taken into account. This required a change of our socket emulation
code and changed the IPC protocol under Windows.
See-also: gnupg-announce/2007q4/000267.html
Noteworthy changes in version 2.0.7 (2007-09-10)
------------------------------------------------
* Fixed encryption problem if duplicate certificates are in the
keybox.
* Made it work on Windows Vista. Note that the entire Windows port
is still considered Beta.
* Add new options min-passphrase-nonalpha, check-passphrase-pattern,
enforce-passphrase-constraints and max-passphrase-days to
gpg-agent.
* Add command --check-components to gpgconf. Gpgconf now uses the
installed versions of the programs and does not anymore search via
PATH for them.
See-also: gnupg-announce/2007q3/000259.html
Noteworthy changes in version 2.0.6 (2007-08-16)
------------------------------------------------
* GPGSM does now grok --default-key.
* GPGCONF is now aware of --default-key and --encrypt-to.
* GPGSM does again correctly print the serial number as well the the
various keyids. This was broken since 2.0.4.
* New option --validation-model and support for the chain-model.
* Improved Windows support.
See-also: gnupg-announce/2007q3/000258.html
Noteworthy changes in version 2.0.5 (2007-07-05)
------------------------------------------------
* Switched license to GPLv3.
* Basic support for Windows. Run "./autogen.sh --build-w32" to build
it. As usual the mingw cross compiling toolchain is required.
* Fixed bug when using the --p12-charset without --armor.
* The command --gen-key may now be used instead of the
gpgsm-gencert.sh script.
* Changed key generation to reveal less information about the
machine. Bug fixes for gpg2's card key generation.
See-also: gnupg-announce/2007q3/000255.html
Noteworthy changes in version 2.0.4 (2007-05-09)
------------------------------------------------
* The server mode key listing commands are now also working for
systems without the funopen/fopencookie API.
* PKCS#12 import now tries several encodings in case the passphrase
was not utf-8 encoded. New option --p12-charset for gpgsm.
* Improved the libgcrypt logging support in all modules.
See-also: gnupg-announce/2007q2/000254.html
Noteworthy changes in version 2.0.3 (2007-03-08)
------------------------------------------------
* By default, do not allow processing multiple plaintexts in a single
stream. Many programs that called GnuPG were assuming that GnuPG
did not permit this, and were thus not using the plaintext boundary
status tags that GnuPG provides. This change makes GnuPG reject
such messages by default which makes those programs safe again.
--allow-multiple-messages returns to the old behavior. [CVE-2007-1263].
* New --verify-option show-primary-uid-only.
* gpgconf may now reads a global configuration file to select which
options are changeable by a frontend. The new applygnupgdefaults
tool may be used by an admin to set default options for all users.
* The PIN pad of the Cherry XX44 keyboard is now supported. The
DINSIG and the NKS applications are now also aware of PIN pads.
See-also: gnupg-announce/2007q1/000252.html
Noteworthy changes in version 2.0.2 (2007-01-31)
------------------------------------------------
* Fixed a serious and exploitable bug in processing encrypted
packages. [CVE-2006-6235].
* Added --passphrase-repeat to set the number of times GPG will
prompt for a new passphrase to be repeated. This is useful to help
memorize a new passphrase. The default is 1 repetition.
* Using a PIN pad does now also work for the signing key.
* A warning is displayed by gpg-agent if a new passphrase is too
short. New option --min-passphrase-len defaults to 8.
* The status code BEGIN_SIGNING now shows the used hash algorithms.
See-also: gnupg-announce/2007q1/000249.html
Noteworthy changes in version 2.0.1 (2006-11-28)
------------------------------------------------
* Experimental support for the PIN pads of the SPR 532 and the Kaan
Advanced card readers. Add "disable-keypad" scdaemon.conf if you
don't want it. Does currently only work for the OpenPGP card and
its authentication and decrypt keys.
* Fixed build problems on some some platforms and crashes on amd64.
* Fixed a buffer overflow in gpg2. [bug#728,CVE-2006-6169]
See-also: gnupg-announce/2006q4/000242.html
Noteworthy changes in version 2.0.0 (2006-11-11)
------------------------------------------------
* First stable version of a GnuPG integrating OpenPGP and S/MIME.
See-also: gnupg-announce/2006q4/000239.html
Noteworthy changes in version 1.9.95 (2006-11-06)
-------------------------------------------------
* Minor bug fixes.
Noteworthy changes in version 1.9.94 (2006-10-24)
-------------------------------------------------
* Keys for gpgsm may now be specified using a keygrip. A keygrip is
indicated by a prefixing it with an ampersand.
* gpgconf now supports switching the CMS cipher algo (e.g. to AES).
* New command --gpgconf-test for all major tools. This may be used to
check whether the configuration file is sane.
Noteworthy changes in version 1.9.93 (2006-10-18)
-------------------------------------------------
* In --with-validation mode gpgsm will now also ask whether a root
certificate should be trusted.
* Link to Pth only if really necessary.
* Fixed a pubring corruption bug in gpg2 occurring when importing
signatures or keys with insane lengths.
* Fixed v3 keyID calculation bug in gpg2.
* More tweaks for certificates without extensions.
Noteworthy changes in version 1.9.92 (2006-10-11)
-------------------------------------------------
* Bug fixes.
See-also: gnupg-announce/2006q4/000236.html
Noteworthy changes in version 1.9.91 (2006-10-04)
-------------------------------------------------
* New "relax" flag for trustlist.txt to allow root CA certificates
without BasicContraints.
* [gpg2] Removed the -k PGP 2 compatibility hack. -k is now an
alias for --list-keys.
* [gpg2] Print a warning if "-sat" is used instead of "--clearsign".
Noteworthy changes in version 1.9.90 (2006-09-25)
-------------------------------------------------
* Made readline work for gpg.
* Cleanups und minor bug fixes.
* Included translations from gnupg 1.4.5.
Noteworthy changes in version 1.9.23 (2006-09-18)
-------------------------------------------------
* Regular man pages for most tools are now build directly from the
Texinfo source.
* The gpg code from 1.4.5 has been fully merged into this release.
The configure option --enable-gpg is still required to build this
gpg part. For production use of OpenPGP the gpg version 1.4.5 is
still recommended. Note, that gpg will be installed under the name
gpg2 to allow coexisting with an 1.4.x gpg.
* API change in gpg-agent's pkdecrypt command. Thus an older gpgsm
may not be used with the current gpg-agent.
* The scdaemon will now call a script on reader status changes.
* gpgsm now allows file descriptor passing for "INPUT", "OUTPUT" and
"MESSAGE".
* The gpgsm server may now output a key listing to the output file
handle. This needs to be enabled using "OPTION list-to-output=1".
* The --output option of gpgsm has now an effect on list-keys.
* New gpgsm commands --dump-chain and list-chain.
* gpg-connect-agent has new options to utilize descriptor passing.
* A global trustlist may now be used. See doc/examples/trustlist.txt.
* When creating a new pubring.kbx keybox common certificates are
imported.
Noteworthy changes in version 1.9.22 (2006-07-27)
-------------------------------------------------
* Enhanced pkcs#12 support to allow import from simple keyBags.
* Exporting to pkcs#12 now create bag attributes so that Mozilla is
able to import the files.
* Fixed uploading of certain keys to the smart card.
Noteworthy changes in version 1.9.21 (2006-06-20)
-------------------------------------------------
* New command APDU for scdaemon to allow using it for general card
access. Might be used through gpg-connect-agent by using the SCD
prefix command.
* Support for the CardMan 4040 PCMCIA reader (Linux 2.6.15 required).
* Scdaemon does not anymore reset cards at the end of a connection.
* Kludge to allow use of Bundesnetzagentur issued X.509 certificates.
* Added --hash=xxx option to scdaemon's PKSIGN command.
* Pkcs#12 files are now created with a MAC. This is for better
interoperability.
* Collected bug fixes and minor other changes.
Noteworthy changes in version 1.9.20 (2005-12-20)
-------------------------------------------------
* Importing pkcs#12 files created be recent versions of Mozilla works
again.
* Basic support for qualified signatures.
* New debug tool gpgparsemail.
Noteworthy changes in version 1.9.19 (2005-09-12)
-------------------------------------------------
* The Belgian eID card is now supported for signatures and ssh.
Other pkcs#15 cards should work as well.
* Fixed bug in --export-secret-key-p12 so that certificates are again
included.
Noteworthy changes in version 1.9.18 (2005-08-01)
-------------------------------------------------
* [gpgsm] Now allows for more than one email address as well as URIs
and dnsNames in certificate request generation. A keygrip may be
given to create a request from an existing key.
* A couple of minor bug fixes.
Noteworthy changes in version 1.9.17 (2005-06-20)
-------------------------------------------------
* gpg-connect-agent has now features to handle Assuan INQUIRE
commands.
* Internal changes for OpenPGP cards. New Assuan command WRITEKEY.
* GNU Pth is now a hard requirement.
* [scdaemon] Support for OpenSC has been removed. Instead a new and
straightforward pkcs#15 modules has been written. As of now it
does allows only signing using TCOS cards but we are going to
enhance it to match all the old capabilities.
* [gpg-agent] New option --write-env-file and Assuan command
UPDATESTARTUPTTY.
* [gpg-agent] New option --default-cache-ttl-ssh to set the TTL for
SSH passphrase caching independent from the other passphrases.
Noteworthy changes in version 1.9.16 (2005-04-21)
-------------------------------------------------
* gpg-agent does now support the ssh-agent protocol and thus allows
to use the pinentry as well as the OpenPGP smartcard with ssh.
* New tool gpg-connect-agent as a general client for the gpg-agent.
* New tool symcryptrun as a wrapper for certain encryption tools.
* The gpg tool is not anymore build by default because those gpg
versions available in the gnupg 1.4 series are far more matured.
Noteworthy changes in version 1.9.15 (2005-01-13)
-------------------------------------------------
* Fixed passphrase caching bug.
* Better support for CCID readers; the reader from Cherry RS 6700 USB
does now work.
Noteworthy changes in version 1.9.14 (2004-12-22)
-------------------------------------------------
* [gpg-agent] New option --use-standard-socket to allow the use of a
fixed socket. gpgsm falls back to this socket if GPG_AGENT_INFO
has not been set.
* Ported to MS Windows with some functional limitations.
* New tool gpg-preset-passphrase.
Noteworthy changes in version 1.9.13 (2004-12-03)
-------------------------------------------------
* [gpgsm] New option --prefer-system-dirmngr.
* Minor cleanups and debugging aids.
Noteworthy changes in version 1.9.12 (2004-10-22)
-------------------------------------------------
* [scdaemon] Partly rewrote the PC/SC code.
* Removed the sc-investigate tool. It is now in a separate package
available at ftp://ftp.g10code.com/g10code/gscutils/ .
* [gpg-agent] Fixed logging problem.
Noteworthy changes in version 1.9.11 (2004-10-01)
-------------------------------------------------
* When using --import along with --with-validation, the imported
certificates are validated and only imported if they are fully
valid.
* [gpg-agent] New option --max-cache-ttl.
* [gpg-agent] When used without --daemon or --server, gpg-agent now
check whether a agent is already running and usable.
* Fixed some i18n problems.
Noteworthy changes in version 1.9.10 (2004-07-22)
-------------------------------------------------
* Fixed a serious bug in the checking of trusted root certificates.
* New configure option --enable-agent-pnly allows to build and
install just the agent.
* Fixed a problem with the log file handling.
Noteworthy changes in version 1.9.9 (2004-06-08)
------------------------------------------------
* [gpg-agent] The new option --allow-mark-trusted is now required to
allow gpg-agent to add a key to the trustlist.txt after user
confirmation.
* Creating PKCS#10 requests does now honor the key usage.
Noteworthy changes in version 1.9.8 (2004-04-29)
------------------------------------------------
* [scdaemon] Overhauled the internal CCID driver.
* [scdaemon] Status files named ~/.gnupg/reader_<n>.status are now
written when using the internal CCID driver.
* [gpgsm] New commands --dump-{,secret,external}-keys to show a very
detailed view of the certificates.
* The keybox gets now compressed after 3 hours and ephemeral
stored certificates are deleted after about a day.
* [gpg] Usability fixes for --card-edit. Note, that this has already
been ported back to gnupg-1.3
Noteworthy changes in version 1.9.7 (2004-04-06)
------------------------------------------------
* Instrumented the modules for gpgconf.
* Added support for DINSIG card applications.
* Include the smimeCapabilities attribute with signed messages.
* Now uses the gettext domain "gnupg2" to avoid conflicts with gnupg
versions < 1.9.
Noteworthy changes in version 1.9.6 (2004-03-06)
------------------------------------------------
* Code cleanups and bug fixes.
Noteworthy changes in version 1.9.5 (2004-02-21)
------------------------------------------------
* gpg-protect-tool gets now installed into libexec as it ought to be.
Cleaned up the build system to better comply with the coding
standards.
* [gpgsm] The --import command is now able to autodetect pkcs#12
files and import secret and private keys from this file format.
A new command --export-secret-key-p12 is provided to allow
exporting of secret keys in PKCS\#12 format.
* [gpgsm] The pinentry will now present a description of the key for
whom the passphrase is requested.
* [gpgsm] New option --with-validation to check the validity of key
while listing it.
* New option --debug-level={none,basic,advanced,expert,guru} to map
the debug flags to sensitive levels on a per program base.
Noteworthy changes in version 1.9.4 (2004-01-30)
------------------------------------------------
* Added support for the Telesec NKS 2.0 card application.
* Added simple tool addgnupghome to create .gnupg directories from
/etc/skel/.gnupg.
* Various minor bug fixes and cleanups; mainly gpgsm and gpg-agent
related.
Noteworthy changes in version 1.9.3 (2003-12-23)
------------------------------------------------
* New gpgsm options --{enable,disable}-ocsp to validate keys using
OCSP. This option requires a not yet released DirMngr version.
Default is disabled.
* The --log-file option may now be used to print logs to a socket.
Prefix the socket name with "socket://" to enable this. This does
not work on all systems and falls back to stderr if there is a
problem with the socket.
* The options --encrypt-to and --no-encrypt-to now work the same in
gpgsm as in gpg. Note, they are also used in server mode.
* Duplicated recipients are now silently removed in gpgsm.
Noteworthy changes in version 1.9.2 (2003-11-17)
------------------------------------------------
* On card key generation is no longer done using the --gen-key
command but from the menu provided by the new --card-edit command.
* PINs are now properly cached and there are only 2 PINs visible.
The 3rd PIN (CHV2) is internally synchronized with the regular PIN.
* All kind of other internal stuff.
Noteworthy changes in version 1.9.1 (2003-09-06)
------------------------------------------------
* Support for OpenSC is back. scdaemon supports a --disable-opensc to
disable OpenSC use at runtime, so that PC/SC or ct-API can still be
used directly.
* Rudimentary support for the SCR335 smartcard reader using an
internal driver. Requires current libusb from CVS.
* Bug fixes.
Noteworthy changes in version 1.9.0 (2003-08-05)
------------------------------------------------
====== PLEASE SEE README-alpha =======
* gpg has been renamed to gpg2 and gpgv to gpgv2. This is a
temporary change to allow co-existing with stable gpg versions.
* ~/.gnupg/gpg.conf-1.9.0 is fist tried as config file before the
usual gpg.conf.
* Removed the -k, -kv and -kvv commands. -k is now an alias to
--list-keys. New command -K as alias for --list-secret-keys.
* Removed --run-as-shm-coprocess feature.
* gpg does now also use libgcrypt, libgpg-error is required.
* New gpgsm commands --call-dirmngr and --call-protect-tool.
* Changing a passphrase is now possible using "gpgsm --passwd"
* The content-type attribute is now recognized and created.
* The agent does now reread certain options on receiving a HUP.
* The pinentry is now forked for each request so that clients with
different environments are supported. When running in daemon mode
and --keep-display is not used the DISPLAY variable is ignored.
* Merged stuff from the newpg branch and started this new
development branch.
Version 1.4.19 (2015-02-27)
Version 1.4.18 (2014-06-30)
Version 1.4.17 (2014-06-23)
Version 1.4.16 (2013-12-18)
Version 1.4.15 (2013-10-04)
Version 1.4.14 (2013-07-25)
Version 1.4.13 (2012-12-20)
Version 1.4.12 (2012-01-30)
Version 1.4.11 (2010-10-18)
Version 1.4.10 (2009-09-02)
Version 1.4.9 (2008-03-26)
Version 1.4.8 (2007-12-20)
Version 1.4.7 (2007-03-05)
Version 1.4.6 (2006-12-06)
Version 1.4.5 (2006-08-01)
Version 1.4.4 (2006-06-25)
Version 1.4.3 (2006-04-03)
Version 1.4.2 (2005-07-26)
Version 1.4.1 (2005-03-15)
Version 1.4.0 (2004-12-16)
Noteworthy changes in version 1.3.2 (2003-05-27)
------------------------------------------------
* New "--gnupg" option (set by default) that disables --openpgp,
and the various --pgpX emulation options. This replaces
--no-openpgp, and --no-pgpX, and also means that GnuPG has
finally grown a --gnupg option to make GnuPG act like GnuPG.
* A bug in key validation has been fixed. This bug only affects
keys with more than one user ID (photo IDs do not count here),
and results in all user IDs on a given key being treated with
the validity of the most-valid user ID on that key.
* Notation names that do not contain a '@' are no longer allowed
unless --expert is set. This is to help prevent pollution of
the (as yet unused) IETF notation namespace.
* Multiple trust models are now supported via the --trust-model
option. The options are "pgp" (web-of-trust plus trust
signatures), "classic" (web-of-trust only), and "always"
(identical to the --always-trust option).
* The --personal-{cipher|digest|compression}-preferences are now
consulted to get default algorithms before resorting to the
last-ditch defaults of --s2k-cipher-algo, SHA1, and ZIP
respectively. This allows a user to set algorithms to use in a
safe manner so they are used when legal to do so, without
forcing them on for all messages.
* New --primary-keyring option to designate the keyring that the
user wants new keys imported into.
* --s2k-digest-algo is now used for all password mangling.
Earlier versions used both --s2k-digest-algo and --digest-algo
for passphrase mangling.
* Handling of --hidden-recipient or --throw-keyid messages is now
easier - the user only needs to give their passphrase once, and
GnuPG will try it against all of the available secret keys.
* Care is taken to prevent compiler optimization from removing
memory wiping code.
* New option --no-mangle-dos-filenames so that filenames are not
truncated in the W32 version.
* A "convert-from-106" script has been added. This is a simple
script that automates the conversion from a 1.0.6 or earlier
version of GnuPG to a 1.0.7 or later version.
* Disabled keys are now skipped when selecting keys for
encryption. If you are using the --with-colons key listings to
detect disabled keys, please see doc/DETAILS for a minor format
change in this release.
* Minor trustdb changes to make the trust calculations match
common usage.
* New command "revuid" in the --edit-key menu to revoke a user ID.
This is a simpler interface to the old method (which still
works) of revoking the user ID self-signature.
* Status VALIDSIG does now also print the primary key's
fingerprint, as well as the signature version, pubkey algorithm,
hash algorithm, and signature class.
* Add read-only support for the SHA-256 hash, and optional
read-only support for the SHA-384 and SHA-512 hashes.
* New option --enable-progress-filter for use with frontends.
* DNS SRV records are used in HKP keyserver lookups to allow
administrators to load balance and select keyserver ports
automatically. This is as specified in
draft-shaw-openpgp-hkp-00.txt.
* When using the "keyid!" syntax during a key export, only that
specified key is exported. If the key in question is a subkey,
the primary key plus only that subkey is exported.
* configure --disable-xxx options to disable individual algorithms
at build time. This can be used to build a smaller gpg binary
for embedded uses where space is tight. See the README file for
the algorithms that can be used with this option, or use
--enable-minimal to build the smallest gpg possible (disables
all optional algorithms, disables keyserver access, and disables
photo IDs).
* The keyserver no-modify flag on a key can now be displayed and
modified.
* Note that the TIGER/192 digest algorithm is in the process of
being dropped from the OpenPGP standard. While this release of
GnuPG still contains it, it is disabled by default. To ensure
you will still be able to use your messages with future versions
of GnuPG and other OpenPGP programs, please do not use this
algorithm.
See-also: gnupg-announce/2003q2/000153.html
Noteworthy changes in version 1.3.1 (2002-11-12)
------------------------------------------------
* Trust signature support. This is based on the Maurer trust
model where a user can specify the trust level along with the
signature with multiple levels so users can delegate
certification ability to other users, possibly restricted by a
regular expression on the user ID. Note that full trust
signature support requires a regular expression parsing library.
The regexp code from glibc 2.3.1 is included for those platforms
that don't have working regexp functions available. The
configure option --disable-regex may be used to disable any
regular expression code, which will make GnuPG ignore any trust
signature with a regular expression included.
* Two new commands --hidden-recipient (-R) and --hidden-encrypt-to
encrypt to a user, but hide the identity of that user. This is
the same functionality as --throw-keyid, but can be used on a
per-user basis.
* Full algorithm names (e.g. "3DES", "SHA1", "ZIP") can now be
used interchangeably with the short algorithm names (e.g. "S2",
"H2", "Z1") anywhere algorithm names are used in GnuPG.
Noteworthy changes in version 1.3.0 (2002-10-18)
------------------------------------------------
* The last piece of internal keyserver support has been removed,
and now all keyserver access is done via the keyserver plugins.
There is also a newer keyserver protocol used between GnuPG and
the plugins, so plugins from earlier versions of GnuPG may not
work properly.
* The HKP keyserver plugin supports the new machine-readable key
listing format for those keyservers that provide it.
* When using a HKP keyserver with multiple DNS records (such as
wwwkeys.pgp.net which has the addresses of multiple servers
around the world), try all records until one succeeds. Note
that it depends on the LDAP library used whether the LDAP
keyserver plugin does this as well.
* The library dependencies for OpenLDAP seem to change fairly
frequently, and GnuPG's configure script cannot guess all the
combinations. Use ./configure LDAPLIBS="-L libdir -l libs" to
override the script and use the libraries selected.
* Secret keys generated with --export-secret-subkeys are now
indicated in key listings with a '#' after the "sec", and in
--with-colons listings by showing no capabilities (no lowercase
characters).
* --trusted-key has been un-obsoleted, as it is useful for adding
ultimately trusted keys from the config file. It is identical
to using --edit and "trust" to change a key to ultimately
trusted.
* Translations other than de are no longer distributed with the
development branch. This is due to the frequent text changes
during development, which cause the translations to rapidly go
out of date.
Version 1.2.8 (2006-12-07)
Version 1.2.7 (2004-12-27)
Version 1.2.6 (2004-08-25)
Version 1.2.5 (2004-07-26)
Version 1.2.4 (2003-12-23)
Version 1.2.3 (2003-08-21)
Version 1.2.2 (2003-05-01)
Version 1.2.1 (2002-10-25)
Version 1.2.0 (2002-09-21)
Noteworthy changes in version 1.1.92 (2002-09-11)
-------------------------------------------------
* [IMPORTANT] The default configuration file is now
~/.gnupg/gpg.conf. If an old ~/.gnupg/options is found it will
still be used. This change is required to have a more
consistent naming scheme with forthcoming tools.
* The use of MDCs have increased. A MDC will be used if the
recipients directly request it, if the recipients have AES,
AES192, AES256, or TWOFISH in their cipher preferences, or if
the chosen cipher has a blocksize not equal to 64 bits
(currently this is also AES, AES192, AES256, and TWOFISH).
* GnuPG will no longer automatically disable compression when
processing an already-compressed file unless a MDC is being
used. This is to give the message a certain amount of
resistance to the chosen-ciphertext attack while communicating
with other programs (most commonly PGP earlier than version 7.x)
that do not support MDCs.
* The option --interactive now has the desired effect when
importing keys.
* The file permission and ownership checks on files have been
clarified. Specifically, the homedir (usually ~/.gnupg) is
checked to protect everything within it. If the user specifies
keyrings outside this homedir, they are presumed to be shared
keyrings and therefore *not* checked. Configuration files
specified with the --options option and the IDEA cipher
extension specified with --load-extension are checked, along
with their enclosing directories.
* The configure option --with-static-rnd=auto allows to build gpg
with all available entropy gathering modules included. At
runtime the best usable one will be selected from the list
linux, egd, unix. This is also the default for systems lacking
a /dev/random device.
* The default character set is now taken from the current locale;
it can still be overridden by the --charset option. Using the
option -vvv shows the used character set.
* [REMOVED] --emulate-checksum-bug and --emulate-3des-s2k-bug have
been removed.
Noteworthy changes in version 1.1.91 (2002-08-04)
-------------------------------------------------
* All modules are now linked statically; the --load-extension
option is in general not useful anymore. The only exception is
to specify the deprecated idea cipher.
* The IDEA plugin has changed. Previous versions of the IDEA
plugin will no longer work with GnuPG. However, the current
version of the plugin will work with earlier GnuPG versions.
* When using --batch with one of the --delete-key commands, the
key must be specified by fingerprint. See the man page for
details.
* There are now various ways to restrict the ability GnuPG has to
exec external programs (for the keyserver helpers or photo ID
viewers). Read the README file for the complete list.
* New export option to leave off attribute packets (photo IDs)
during export. This is useful when exporting to HKP keyservers
which do not understand attribute packets.
* New import option to repair during import the HKP keyserver
mangling multiple subkeys bug. Note that this cannot completely
repair the damaged key as some crucial data is removed by the
keyserver, but it does at least give you back one subkey. This
is on by default for keyserver --recv-keys, and off by default
for regular --import.
* The keyserver helper programs now live in
/usr/[local/]libexec/gnupg by default. If you are upgrading
from 1.0.7, you might want to delete your old copies in
/usr/[local/]bin. If you use an OS that does not use libexec
for whatever reason, use configure --libexecdir=/usr/local/lib
to place the keyserver helpers there.
* The LDAP keyserver handler now works properly with very old
(version 1) LDAP keyservers.
Noteworthy changes in version 1.1.90 (2002-07-01)
-------------------------------------------------
* New commands: --personal-cipher-preferences,
--personal-digest-preferences, and
--personal-compress-preferences allow the user to specify which
algorithms are to be preferred. Note that this does not permit
using an algorithm that is not present in the recipient's
preferences (which would violate the OpenPGP standard). This
just allows sorting the preferences differently.
* New "group" command to refer to several keys with one name.
* A warning is issued if the user forces the use of an algorithm
that is not listed in the recipient's preferences.
* Full revocation key (aka "designated revoker") support.
* The preferred hash algorithms on a key are consulted when
encrypting a signed message to that key. Note that this is
disabled by default by a SHA1 preference in
--personal-digest-preferences.
* --cert-digest-algo allows the user to specify the hash algorithm
to use when signing a key rather than the default SHA1 (or MD5
for PGP2 keys). Do not use this feature unless you fully
understand the implications of this.
* --pgp7 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 7.x.
* New --attribute-fd command for frontends and scripts to get the
contents of attribute packets (i.e. photos)
* In expert mode, the user can now re-sign a v3 key with a v4
self-signature. This does not change the v3 key into a v4 key,
but it does allow the user to use preferences, primary ID flags,
etc.
* Significantly improved photo ID support on non-unixlike
platforms.
* The version number has jumped ahead to 1.1.90 to skip over the
old version 1.1 and to get ready for the upcoming 1.2.
* ElGamal sign and encrypt is not anymore allowed in the key
generation dialog unless in expert mode. RSA sign and encrypt
has been added with the same restrictions.
* [W32] Keyserver access does work with Windows NT.
Noteworthy changes in version 1.0.7 (2002-04-29)
------------------------------------------------
* Secret keys are now stored and exported in a new format which
uses SHA-1 for integrity checks. This format renders the
Rosa/Klima attack useless. Other OpenPGP implementations might
not yet support this, so the option --simple-sk-checksum creates
the old vulnerable format.
* The default cipher algorithm for encryption is now CAST5,
default hash algorithm is SHA-1. This will give us better
interoperability with other OpenPGP implementations.
* Symmetric encrypted messages now use a fixed file size if
possible. This is a tradeoff: it breaks PGP 5, but fixes PGP 2,
6, and 7. Note this was only an issue with RFC-1991 style
symmetric messages.
* Photographic user ID support. This uses an external program to
view the images.
* Enhanced keyserver support via keyserver "plugins". GnuPG comes
with plugins for the NAI LDAP keyserver as well as the HKP email
keyserver. It retains internal support for the HKP HTTP
keyserver.
* Nonrevocable signatures are now supported. If a user signs a
key nonrevocably, this signature cannot be taken back so be
careful!
* Multiple signature classes are usable when signing a key to
specify how carefully the key information (fingerprint, photo
ID, etc) was checked.
* --pgp2 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 2.x.
* --pgp6 mode automatically sets all necessary options to ensure
that the resulting message will be usable by a user of PGP 6.x.
* Signatures may now be given an expiration date. When signing a
key with an expiration date, the user is prompted whether they
want their signature to expire at the same time.
* Revocation keys (designated revokers) are now supported if
present. There is currently no way to designate new keys as
designated revokers.
* Permissions on the .gnupg directory and its files are checked
for safety.
* --expert mode enables certain silly things such as signing a
revoked user id, expired key, or revoked key.
* Some fixes to build cleanly under Cygwin32.
* New tool gpgsplit to split OpenPGP data formats into packets.
* New option --preserve-permissions.
* Subkeys created in the future are not used for encryption or
signing unless the new option --ignore-valid-from is used.
* Revoked user-IDs are not listed unless signatures are listed too
or we are in verbose mode.
* There is no default comment string with ascii armors anymore
except for revocation certificates and --enarmor mode.
* The command "primary" in the edit menu can be used to change the
primary UID, "setpref" and "updpref" can be used to change the
preferences.
* Fixed the preference handling; since 1.0.5 they were erroneously
matched against against the latest user ID and not the given one.
* RSA key generation.
* Merged Stefan's patches for RISC OS in. See comments in
scripts/build-riscos.
* It is now possible to sign and conventional encrypt a message (-cs).
* The MDC feature flag is supported and can be set by using
the "updpref" edit command.
* The status messages GOODSIG and BADSIG are now returning the primary
UID, encoded using %XX escaping (but with spaces left as spaces,
so that it should not break too much)
* Support for GDBM based keyrings has been removed.
* The entire keyring management has been revamped.
* The way signature stati are store has changed so that v3
signatures can be supported. To increase the speed of many
operations for existing keyrings you can use the new
--rebuild-keydb-caches command.
* The entire key validation process (trustdb) has been revamped.
See the man page entries for --update-trustdb, --check-trustdb
and --no-auto-check-trustdb.
* --trusted-keys is again obsolete, --edit can be used to set the
ownertrust of any key to ultimately trusted.
* A subkey is never used to sign keys.
* Read only keyrings are now handled as expected.
See-also: gnupg-announce/2002q2/000135.html
Noteworthy changes in version 1.0.6 (2001-05-29)
------------------------------------------------
* Security fix for a format string bug in the tty code.
* Fixed format string bugs in all PO files.
* Removed Russian translation due to too many bugs. The FTP
server has an unofficial but better translation in the contrib
directory.
* Fixed expire time calculation and keyserver access.
* The usual set of minor bug fixes and enhancements.
* non-writable keyrings are now correctly handled.
See-also: gnupg-announce/2001q2/000123.html
Noteworthy changes in version 1.0.5 (2001-04-29)
------------------------------------------------
* WARNING: The semantics of --verify have changed to address a
problem with detached signature detection. --verify now ignores
signed material given on stdin unless this is requested by using
a "-" as the name for the file with the signed material. Please
check all your detached signature handling applications and make
sure that they don't pipe the signed material to stdin without
using a filename together with "-" on the the command line.
* WARNING: Corrected hash calculation for input data larger than
512M - it was just wrong, so you might notice bad signature in
some very big files. It may be wise to keep an old copy of
GnuPG around.
* Secret keys are no longer imported unless you use the new option
--allow-secret-key-import. This is a kludge and future versions will
handle it in another way.
* New command "showpref" in the --edit-key menu to show an easier
to understand preference listing.
* There is now the notation of a primary user ID. For example, it
is printed with a signature verification as the first user ID;
revoked user IDs are not printed there anymore. In general the
primary user ID is the one with the latest self-signature.
* New --charset=utf-8 to bypass all internal conversions.
* Large File Support (LFS) is now working.
* New options: --ignore-crc-error, --no-sig-create-check,
--no-sig-cache, --fixed-list-mode, --no-expensive-trust-checks,
--enable-special-filenames and --use-agent. See man page.
* New command --pipemode, which can be used to run gpg as a
co-process. Currently only the verification of detached
signatures are working. See doc/DETAILS.
* Keyserver support for the W32 version.
* Rewritten key selection code so that GnuPG can better cope with
multiple subkeys, expire dates and so. The drawback is that it
is slower.
* A whole lot of bug fixes.
* The verification status of self-signatures are now cached. To
increase the speed of key list operations for existing keys you
can do the following in your GnuPG homedir (~/.gnupg):
cp pubring.gpg pubring.gpg.save && gpg --export-all >x && \
rm pubring.gpg && gpg --import x
Only v4 keys (i.e not the old RSA keys) benefit from this caching.
* New translations: Estonian, Turkish.
See-also: gnupg-announce/2001q2/000122.html
Noteworthy changes in version 1.0.4 (2000-10-17)
------------------------------------------------
* Fixed a serious bug which could lead to false signature verification
results when more than one signature is fed to gpg. This is the
primary reason for releasing this version.
* New utility gpgv which is a stripped down version of gpg to
be used to verify signatures against a list of trusted keys.
* Rijndael (AES) is now supported and listed with top preference.
* --with-colons now works with --print-md[s].
See-also: gnupg-announce/2000q4/000082.html
Noteworthy changes in version 1.0.3 (2000-09-18)
------------------------------------------------
* Fixed problems with piping to/from other MS-Windows software
* Expiration time of the primary key can be changed again.
* Revoked user IDs are now marked in the output of --list-key
* New options --show-session-key and --override-session-key
to help the British folks to somewhat minimize the danger
of this Orwellian RIP bill.
* New options --merge-only and --try-all-secrets.
* New configuration option --with-egd-socket.
* The --trusted-key option is back after it left us with 0.9.5
* RSA is supported. Key generation does not yet work but will come
soon.
* CAST5 and SHA-1 are now the default algorithms to protect the key
and for symmetric-only encryption. This should solve a couple
of compatibility problems because the old algorithms are optional
according to RFC2440
* Twofish and MDC enhanced encryption is now used. PGP 7 supports
this. Older versions of GnuPG don't support it, so they should be
upgraded to at least 1.0.2
See-also: gnupg-announce/2000q3/000075.html
Noteworthy changes in version 1.0.2 (2000-07-12)
----------------------------------------------
* Fixed expiration handling of encryption keys.
* Add an experimental feature to do unattended key generation.
* The user is now asked for the reason of revocation as required
by the new OpenPGP draft.
* There is a ~/.gnupg/random_seed file now which saves the
state of the internal RNG and increases system performance
somewhat. This way the full entropy source is only used in
cases were it is really required.
Use the option --no-random-seed-file to disable this feature.
* New options --ignore-time-conflict and --lock-never.
* Some fixes for the W32 version.
* The entropy.dll is not anymore used by the W32 version but replaced
by code derived from Cryptlib.
* Encryption is now much faster: About 2 times for 1k bit keys
and 8 times for 4k keys.
* New encryption keys are generated in a way which allows a much
faster decryption.
* New command --export-secret-subkeys which outputs the
the _primary_ key with it's secret parts deleted. This is
useful for automated decryption/signature creation as it
allows to keep the real secret primary key offline and
thereby protecting the key certificates and allowing to
create revocations for the subkeys. See the FAQ for a
procedure to install such secret keys.
* Keygeneration now writes to the first writeable keyring or
as default to the one in the homedirectory. Prior versions
ignored all --keyring options.
* New option --command-fd to take user input from a file descriptor;
to be used with --status-fd by software which uses GnuPG as a backend.
* There is a new status PROGRESS which is used to show progress during
key generation.
* Support for the new MDC encryption packets. To create them either
--force-mdc must be use or cipher algorithm with a blocksize other
than 64 bits is to be used. --openpgp currently disables MDC packets
entirely. This option should not yet be used.
* New option --no-auto-key-retrieve to disable retrieving of
a missing public key from a keyserver, when a keyserver has been set.
* Danish translation
See-also: gnupg-announce/2000q3/000069.html
Noteworthy changes in version 1.0.1 (1999-12-16)
-----------------------------------
* New command --verify-files. New option --fast-list-mode.
* $http_proxy is now used when --honor-http-proxy is set.
* Fixed some minor bugs and the problem with conventional encrypted
packets which did use the gpg v3 partial length headers.
* Add Indonesian and Portuguese translations.
* Fixed a bug with symmetric-only encryption using the non-default 3DES.
The option --emulate-3des-s2k-bug may be used to decrypt documents
which have been encrypted this way; this should be done immediately
as this workaround will be remove in 1.1
* Can now handle (but not display) PGP's photo IDs. I don't know the
format of that packet but after stripping a few bytes from the start
it looks like a JPEG (at least my test data). Handling of this
package is required because otherwise it would mix up the
self signatures and you can't import those keys.
* Passing non-ascii user IDs on the commandline should now work in all
cases.
* New keys are now generated with an additional preference to Blowfish.
* Removed the GNU Privacy Handbook from the distribution as it will go
into a separate one.
See-also: gnupg-announce/1999q4/000050.html
Noteworthy changes in version 1.0.0 (1999-09-07)
-----------------------------------
* Add a very preliminary version of the GNU Privacy Handbook to
the distribution (lynx doc/gph/index.html).
* Changed the version number to GnuPG 2001 ;-)
See-also: gnupg-announce/1999q3/000037.html
Noteworthy changes in version 0.9.11 (1999-09-03)
------------------------------------
* UTF-8 strings are now correctly printed (if --charset is set correctly).
Output of --with-colons remains C-style escaped UTF-8.
* Workaround for a problem with PGP 5 detached signature in textmode.
* Fixed a problem when importing new subkeys (duplicated signatures).
See-also: gnupg-announce/1999q3/000036.html
Noteworthy changes in version 0.9.10 (1999-07-23)
------------------------------------
* Some strange new options to help pgpgpg
* Cleaned up the dox a bit.
See-also: gnupg-announce/1999q3/000034.html
Noteworthy changes in version 0.9.9
-----------------------------------
* New options --[no-]utf8-strings.
* New edit-menu commands "enable" and "disable" for entire keys.
* You will be asked for a filename if gpg cannot deduce one.
* Changes to support libtool which is needed for the development
of libgcrypt.
* New script tools/lspgpot to help transferring assigned
trustvalues from PGP to GnuPG.
* New commands --lsign-key and made --sign-key a shortcut for --edit
and sign.
* New options (#122--126 ;-) --[no-]default-recipient[-self],
--disable-{cipher,pubkey}-algo. See the man page.
* Enhanced info output in case of multiple recipients and fixed exit code.
* New option --allow-non-selfsigned-uid to work around a problem with
the German IN way of separating signing and encryption keys.
See-also: gnupg-announce/1999q3/000028.html
Noteworthy changes in version 0.9.8 (1999-06-26)
-----------------------------------
* New subcommand "delsig" in the edit menu.
* The name of the output file is not anymore the one which is
embedded in the processed message, but the used filename with
the extension stripped. To revert to the old behaviour you can
use the option --use-embedded-filename.
* Another hack to cope with pgp2 generated detached signatures.
* latin-2 character set works (--charset=iso-8859-2).
* New option --with-key-data to list the public key parameters.
New option -N to insert notations and a --set-policy-url.
A couple of other options to allow resetting of options.
* Better support for HPUX.
See-also: gnupg-announce/1999q2/000016.html
Noteworthy changes in version 0.9.7 (1999-05-23)
-----------------------------------
* Add some work arounds for a bugs in pgp 2 which led to bad signatures
when used with canonical texts in some cases.
* Enhanced some status outputs.
See-also: gnupg-announce/1999q2/000000.html
Noteworthy changes in version 0.9.6 (1999-05-06)
-----------------------------------
* Twofish is now statically linked by default. The experimental 128 bit
version is now disabled. Full support will be available as soon as
the OpenPGP WG has decided on an interpretation of rfc2440.
* Dropped support for the ancient Blowfish160 which is not OpenPGP.
* Merged gpgm and gpg into one binary.
* Add "revsig" and "revkey" commands to the edit menu. It is now
possible to revoke signature and subkeys.
Noteworthy changes in version 0.9.5 (1999-03-20)
-----------------------------------
* New command "lsign" in the keyedit menu to create non-exportable
signatures. Removed --trusted-keys option.
* A bunch of changes to the key validation code.
* --list-trust-path now has an optional --with-colons format.
* New command --recv-keys to import keys from an keyserver.
Noteworthy changes in version 0.9.4 (1999-03-08)
-----------------------------------
* New configure option --enable-static-rnd=[egd|linux|unix|none]
to select a random gathering module for static linking.
* The original text is now verbatim copied to a cleartext signed message.
* Bugfixes but there are still a couple of bugs.
Noteworthy changes in version 0.9.3 (1999-02-19)
-----------------------------------
* Changed the internal design of getkey which now allows a
efficient lookup of multiple keys and add a word match mode.
* New options --[no-]encrypt-to.
* Some changes to the configure stuff. Switched to automake 1.4.
Removed intl/ from CVS, autogen.sh now uses gettextize.
* Preferences now include Twofish. Removed preference to Blowfish with
a special hack to suppress the "not listed in preferences" warning;
this is to allow us to switch completely to Twofish in the near future.
* Changed the locking stuff.
* Print all user ids of a good signature.
Noteworthy changes in version 0.9.2 (1999-01-01)
-----------------------------------
* add some additional time warp checks.
* Option --keyserver and command --send-keys to utilize HKP servers.
* Upgraded to zlib 1.1.3 and fixed an inflate bug
* More cleanup on the cleartext signatures.
Noteworthy changes in version 0.9.1 (1999-01-01)
-----------------------------------
* Polish language support.
* When querying the passphrase, the key ID of the primary key is
displayed along with the one of the used secondary key.
* Fixed a bug occurring when decrypting pgp 5 encrypted messages,
fixed an infinite loop bug in the 3DES code and in the code
which looks for trusted signatures.
* Fixed a bug in the mpi library which caused signatures not to
compare okay.
* Rewrote the handling of cleartext signatures; the code is now
better maintainable (I hope so).
* New status output VALIDSIG only for valid signatures together
with the fingerprint of the signer's key.
Noteworthy changes in version 0.9.0 (1998-12-23)
-----------------------------------
* --export does now only exports rfc2440 compatible keys; the
old behaviour is available with --export-all.
Generation of v3 ElGamal (sign and encrypt) keys is not longer
supported.
* Fixed the uncompress bug.
* Rewrote the rndunix module. There are two environment variables
used for debugging now: GNUPG_RNDUNIX_DBG give the file to write
debugging information (use "-" for stdout) and if GNUPG_RNDUNIX_DBGALL
is set, all programs which are only tried are also printed.
* New option --escape-from-lines to "dash-escape" "From " lines to
prevent mailers to change them to ">From ". This is not enabled by
default because it is not in compliance with rfc2440 - however, you
should turn it on.
Noteworthy changes in version 0.4.5 (1998-12-08)
-----------------------------------
* The keyrings and the trustdb is now locked, so that
other GnuPG processes won't damage these files. You
may want to put the option --lock-once into your options file.
* The latest self-signatures are now used; this enables --import
to see updated preferences etc.
* Import of subkeys should now work.
* Random gathering modules may now be loaded as extensions. Add
such a module for most Unices but it is very experimental!
* Brazilian language support.
Noteworthy changes in version 0.4.4 (1998-11-20)
-----------------------------------
* Fixed the way the key expiration time is stored. If you have
an expiration time on your key you should fix it with --edit-key
and the command "expire". I apologize for this inconvenience.
* Add option --charset to support "koi8-r" encoding of user ids.
(Not yet tested).
* Preferences should now work again. You should run
"gpgm --check-trustdb \*" to rebuild all preferences.
* Checking of certificates should now work but this needs a lot
of testing. Key validation values are now cached in the
trustdb; they should be recalculated as needed, but you may
use --check-trustdb or --update-trustdb to do this.
* Spanish translation by Urko Lusa.
* Patch files are from now on signed. See the man page
for the new option --not-dash-escaped.
* New syntax: --edit-key <userID> [<commands>]
If you run it without --batch the commands are executed and then
you are put into normal mode unless you use "quit" or "save" as
one of the commands. When in batch mode, the program quits after
the last command, so you have to use "save" if you did some changes.
It does not yet work completely, but may be used to list so the
keys etc.
Noteworthy changes in version 0.4.3 (1998-11-08)
-----------------------------------
* Fixed the gettext configure bug.
* Kludge for RSA keys: keyid and length of a RSA key are
correctly reported, but you get an error if you try to use
this key (If you do not have the non-US version).
* Experimental support for keyrings stored in a GDBM database.
This is *much* faster than a standard keyring. You will notice
that the import gets slower with time; the reason is that all
new keys are used to verify signatures of previous inserted
keys. Use "--keyring gnupg-gdbm:<name-of-gdbm-file>". This is
not (yet) supported for secret keys.
* A Russian language file in the distribution (alternatives are in
the contrib directory of the FTP servers)
* commandline option processing now works as expected for GNU programs
with the exception that you can't mix options and normal arguments.
* Now --list-key lists all matching keys. This is needed in some
other places too.
Noteworthy changes in version 0.4.2 (1998-10-18)
-----------------------------------
* This is only a snapshot: There are still a few bugs.
* Fixed this huge memory leak.
* Redesigned the trust database: You should run "gpgm --check-trustdb".
New command --update-trustdb, which adds new key from the public
keyring into your trustdb
* Fixed a bug in the armor code, leading to invalid packet errors.
(a workaround for this was to use --no-armor). The shorten line
length (64 instead of 72) fixes a problem with pgp5 and keyservers.
* comment packets are not anymore generated. "--export" filters
them out. One Exception: The comment packets in a secret keyring
are still used because they carry the factorization of the public
prime product.
* --import now only looks for KEYBLOCK headers, so you can now simply
remove the "- " in front of such a header if someone accidentally signed
such a message or the keyblock is part of a cleartext signed message.
* --with-colons now lists the key expiration time and not anymore
the valid period.
* Some keyblocks created with old releases have a wrong sequence
of packets, so that the keyservers don't accept these keys.
Simply using "--edit-key" fixes the problem.
* New option --force-v3-sigs to generate signed messages which are
compatible to PGP 5.
* Add some code to support DLD (for non ELF systems) - but this is
not tested because my BSD box is currently broken.
* New command "expire" in the edit-key menu.
Noteworthy changes in version 0.4.1 (1998-10-07)
-----------------------------------
* A secondary key is used when the primary key is specified but cannot
be used for the operation (if it is a sign-only key).
* GNUPG can now handle concatenated armored messages: There is still a
bug if different kinds of messages are mixed.
* Iterated+Salted passphrases now work. If want to be sure that PGP5
is able to handle them you may want to use the options
"--s2k-mode 3 --s2k-cipher-algo cast5 --s2k-digest-algo sha1"
when changing a passphrase.
* doc/OpenPGP talks about OpenPGP compliance, doc/HACKING gives
a few hints about the internal structure.
* Checked gnupg against the August 1998 draft (07) and I believe
it is in compliance with this document (except for one point).
* Fixed some bugs in the import merging code and rewrote some
code for the trustdb.
Noteworthy changes in version 0.4.0 (1998-09-18)
-----------------------------------
* Triple DES is now supported. Michael Roth did this piece of
needed work. We have now all the coded needed to be OpenPGP
compliant.
* Added a simple rpm spec file (see INSTALL).
* detached and armored signatures are now using "PGP SIGNATURE",
except when --rfc1991 is used.
* All times which are not in the yyyy-mm-dd format are now printed
in local time.
Noteworthy changes in version 0.3.5 (1998-09-14)
-----------------------------------
* New option --throw-keyid to create anonymous enciphered messages.
If gpg detects such a message it tires all available secret keys
in turn so decode it. This is a gnupg extension and not in OpenPGP
but it has been discussed there and afaik some products use this
scheme too (Suggested by Nimrod Zimmerman).
* Fixed a bug with 5 byte length headers.
* --delete-[secret-]key is now also available in gpgm.
* cleartext signatures are not anymore converted to LF only.
* Fixed a trustdb problem. Run "gpgm --check-trustdb" to fix old
trust dbs.
* Building in another directory should now work.
* Weak key detection mechanism (Niklas Hernaeus).
Noteworthy changes in version 0.3.4 (1998-08-11)
-----------------------------------
* New options --comment and --set-filename; see g10/OPTIONS
* yes/no, y/n localized.
* Fixed some bugs.
Noteworthy changes in version 0.3.3 (1998-08-08)
-----------------------------------
* IMPORTANT: I found yet another bug in the way the secret keys
are encrypted - I did it the way pgp 2.x did it, but OpenPGP
and pgp 5.x specify another (in some aspects simpler) method.
To convert your secret keys you have to do this:
1. Build the new release but don't install it and keep
a copy of the old program.
2. Disable the network, make sure that you are the only
user, be sure that there are no Trojan horses etc ....
3. Use your old gpg (version 0.3.1 or 0.3.2) and set the
passphrases of ALL your secret keys to empty!
(gpg --change-passphrase your-user-id).
4. Save your ownertrusts (see the next point)
5. rm ~/.gnupg/trustdb.gpg
6. install the new version of gpg (0.3.3)
7. For every secret key call "gpg --edit-key your-user-id",
enter "passwd" at the prompt, follow the instructions and
change your password back, enter "save" to store it.
8. Restore the ownertrust (see next point).
* The format of the trust database has changed; you must delete
the old one, so gnupg can create a new one.
IMPORTANT: Use version 0.3.1 or .2 to save your assigned ownertrusts
("gpgm --list-ownertrust >saved-trust"); then build this new version
and restore the ownertrust with this new version
("gpgm --import-ownertrust saved-trust"). Please note that
--list-ownertrust has been renamed to --export-ownertrust in this
release and it does now only export defined ownertrusts.
* The command --edit-key now provides a commandline driven menu
which can be used for various tasks. --sign-key is only an
an alias to --edit-key and maybe removed in future: use the
command "sign" of this new menu - you can select which user ids
you want to sign.
* Alternate user ids can now be created an signed.
* Owner trust values can now be changed with --edit-key (trust)
* GNUPG can now run as a coprocess; this enables sophisticated
frontends. tools/shmtest.c is a simple sample implementation.
This needs some more work: all tty_xxx() are to be replaced
by cpr_xxx() and some changes in the display logics is needed.
* Removed options --gen-prime and --gen-random.
* Removed option --add-key; use --edit-key instead.
* Removed option --change-passphrase; use --edit-key instead.
* Signatures are now checked even if the output file could not
be created. Command "--verify" tries to find the detached data.
* gpg now disables core dumps.
* compress and symmetric cipher preferences are now used.
Because there is no 3DES yet, this is replaced by Blowfish.
* We have added the Twofish as an experimental cipher algorithm.
Many thanks to Matthew Skala for doing this work.
Twofish is the AES submission from Schneier et al.; see
"www.counterpane.com/twofish.html" for more information.
* Started with a help system: If you enter a question mark at some
prompt; you should get a specific help for this prompt.
* There is no more backup copy of the secret keyring.
* A lot of new bugs. I think this release is not as stable as
the previous one.
Noteworthy changes in version 0.3.2 (1998-07-09)
-----------------------------------
* Fixed some bugs when using --textmode (-seat)
* Now displays the trust status of a positive verified message.
* Keyrings are now scanned in the sequence they are added with
--[secret-]keyring. Note that the default keyring is implicitly
added as the very first one unless --no-default-keyring is used.
* Fixed setuid and dlopen bug.
Noteworthy changes in version 0.3.1 (1998-07-06)
-----------------------------------
* Partial headers are now written in the OpenPGP format if
a key in a v4 packet is used.
* Removed some unused options, removed the gnupg.sig stuff.
* Key lookup by name now returns a key which can be used for
the desired action.
* New options --list-ownertrust (gpgm) to make a backup copy
of the ownertrust values you assigned.
* clear signature headers are now in compliance with OpenPGP.
Noteworthy changes in version 0.3.0 (1998-06-25)
-----------------------------------
* New option --emulate-checksum-bug. If your passphrase does not
work anymore, use this option and --change-passphrase to rewrite
your passphrase.
* More complete v4 key support: Preferences and expiration time
is set into the self signature.
* Key generation defaults to DSA/ElGamal keys, so that new keys are
interoperable with pgp5
* DSA key generation is faster and key generation does not anymore
remove entropy from the random generator (the primes are public
parameters, so there is really no need for a cryptographic secure
prime number generator which we had used).
* A complete new structure for representing the key parameters.
* Removed most public key knowledge into the cipher library.
* Support for dynamic loading of new algorithms.
* Moved tiger to an extension module.
Noteworthy changes in version 0.2.19 (1998-05-29)
------------------------------------
* Replaced /dev/urandom in checks with new tool mk-tdata.
* Some assembler file cleanups; some more functions for the Alpha.
* Tiger has now the OpenPGP assigned number 6. Because the OID has
changed, old signatures using this algorithm can't be verified.
* gnupg now encrypts the compressed packed and not any longer in the
reverse order; anyway it can decrypt both versions. Thanks to Tom
for telling me this (not security related) bug.
* --add-key works and you are now able to generate subkeys.
* It is now possible to generate ElGamal keys in v4 packets to create
valid OpenPGP keys.
* Some new features for better integration into MUAs.
Noteworthy changes in version 0.2.18 (1998-05-15)
------------------------------------
* Split cipher/random.c, add new option "--disable-dev-random"
to configure to support the development of a random source for
other systems. Prepared sourcefiles rand-unix.c, rand-w32.c
and rand-dummy.c (which is used to allow compilation on systems
without a random source).
* Fixed a small bug in the key generation (it was possible that 48 bits
of a key were not taken from the random pool)
* Add key generation for DSA and v4 signatures.
* Add a function trap_unaligned(), so that a SIGBUS is issued on
Alphas and not the slow emulation code is used. And success: rmd160
raised a SIGBUS.
* Enhanced the formatting facility of argparse and changed the use of
\r,\v to @ because gettext does not like it.
* New option "--compress-algo 1" to allow the creation of compressed
messages which are readable by PGP and "--print-md" (gpgm) to make
speed measurement easier.
Noteworthy changes in version 0.2.17 (1998-05-04)
------------------------------------
* Comment packets are now of private type 61.
* Passphrase code still used a 160 bit blowfish key, added a
silly workaround. Please change your passphrase again - sorry.
* Conventional encryption now uses a type 3 packet to describe the
used algorithms.
* The new algorithm number for Blowfish is 20, 16 is still used for
encryption only; for signing it is only used when it is in a v3 packet,
so that GNUPG keys are still valid.
Noteworthy changes in version 0.2.16 (1998-04-28)
------------------------------------
* Add experimental support for the TIGER/192 message digest algorithm.
(But there is only a dummy ASN OID).
* Standard cipher is now Blowfish with 128 bit key in OpenPGP's CFB
mode. I renamed the old cipher to Blowfish160. Because the OpenPGP
group refused to assign me a number for Blowfish160, I have to
drop support for this in the future. You should use
"--change-passphrase" to recode your current passphrase with 128
bit Blowfish.
Noteworthy changes in version 0.2.15 (1998-04-09)
------------------------------------
* Fixed a bug with the old checksum calculation for secret keys.
If you run the program without --batch, a warning does inform
you if your secret key needs to be converted; simply use
--change-passphrase to recalculate the checksum. Please do this
soon, as the compatible mode will be removed sometime in the future.
* CAST5 works (using the PGP's special CFB mode).
* Again somewhat more PGP 5 compatible.
* Some new test cases
Noteworthy changes in version 0.2.14 (1998-04-02)
------------------------------------
* Changed the internal handling of keyrings.
* Add support to list PGP 5 keyrings with subkeys
* Timestamps of signatures are now verified.
* A expiration time can now be specified during key generation.
* Some speedups for Blowfish and SHA-1, rewrote SHA-1 transform.
Reduced the amount of random bytes needed for key generation in
some cases.
Noteworthy changes in version 0.2.13 (1998-03-10)
------------------------------------
* Verify of DSA signatures works.
* Re-implemented the slower random number generator.
Noteworthy changes in version 0.2.12 (1998-03-07)
------------------------------------
* --delete-key checks that there is no secret key. The new
option --delete-secret-key maybe used to delete a secret key.
* "-kv" now works as expected. Options "--list-{keys,sigs]"
and "--check-sigs" are now working.
* New options "--verify" and "--decrypt" to better support integration
into MUAs (partly done for Mutt).
* New option "--with-colons" to make parsing of key lists easier.
Noteworthy changes in version 0.2.11 (1998-03-02)
------------------------------------
* GPG now asks for a recipient's name if option "-r" is not used.
* If there is no good trust path, the program asks whether to use
the public keys anyway.
* "--delete-key" works for public keys. What semantics shall I use
when there is a secret key too? Delete the secret key or leave him
and auto-regenerate the public key, next time the secret key is used?
Noteworthy changes in version 0.2.10 (1998-02-27)
------------------------------------
* Code for the alpha is much faster (about 20 times); the data
was misaligned and the kernel traps this, so nearly all time
was used by system to trap the misalignments and to write
syslog messages. Shame on me and thanks to Ralph for
pointing me at this while drinking some beer yesterday.
* Changed some configure options and add an option
--disable-m-guard to remove the memory checking code
and to compile everything with optimization on.
* New environment variable GNUPGHOME, which can be used to set
another homedir than ~/.gnupg. Changed default homedir for
Windoze version to c:/gnupg.
* Fixed detached signatures; detached PGP signatures caused a SEGV.
* The Windoze version works (as usual w/o a strong RNG).
Noteworthy changes in version 0.2.9 (1998-02-26)
-----------------------------------
* Fixed FreeBSD bug.
* Added a simple man page.
* Switched to automake1.2f and a newer gettext.
Noteworthy changes in version 0.2.8 (1998-02-24)
-----------------------------------
* Changed the name to GNUPG, the binaries are called gpg and gpgm.
You must rename rename the directory "~/.g10" to ~/.gnupg/, rename
{pub,sec}ring.g10 to {pub,sec}ring.gpg, trustdb.g10 to trustdb.gpg
and g10.sig to gnupg.sig.
* New or changed passphrases are now salted.
Noteworthy changes in version 0.2.7 (1998-02-18)
-----------------------------------
* New command "gen-revoke" to create a key revocation certificate.
* New option "homedir" to set the homedir (which defaults to "~/.g10").
This directory is created if it does not exists (only the last
part of the name and not the complete hierarchy)
* Command "import" works. (Try: "finger gcrypt@ftp.guug.de|g10 --import")
* New commands "dearmor/enarmor" for g10maint. These are mainly
used for internal test purposes.
* Option --version now conforming to the GNU standards and lists
the available ciphers, message digests and public key algorithms.
* Assembler code for m68k (not tested).
* "make check" works.
Noteworthy changes in version 0.2.6 (1998-02-13)
-----------------------------------
* Option "--export" works.
Noteworthy changes in version 0.2.5 (1998-02-12)
-----------------------------------
* Added zlib for systems which don't have it.
Use "./configure --with-zlib" to link with the static version.
* Generalized some more functions and rewrote the encoding of
message digests into MPIs.
* Enhanced the checkit script
Noteworthy changes in version 0.2.4 (1998-02-11)
-----------------------------------
* nearly doubled the speed of the ElGamal signature verification.
* backup copies of keyrings are created.
* assembler stuff for Pentium; gives about 15% better performance.
* fixed a lot of bugs.
Noteworthy changes in version 0.2.3 (1998-02-09)
-----------------------------------
* Found a bug in the calculation of ELG fingerprints. This is now
fixed, but all existing fingerprints and keyids for ELG keys
are not any more valid.
* armor should now work; including clear signed text.
* moved some options to the new program g10maint
* It's now 64 bit clean and runs fine on an alpha--linux.
* Key generation is much faster now. I fixed this by using not
so strong random number for the primes (this was a bug because the
ElGamal primes are public parameters and it does not make sense
to generate them from strong random). The real secret is the x value
which is still generated from strong (okay: /dev/random) random bits.
* added option "--status-fd": see g10/OPTIONS
* We have secure memory on systems which support mlock().
It is not complete yet, because we do not have signal handler
which does a cleanup in very case.
We should also check the ulimit for the user in the case
that the admin does not have set a limit on locked pages.
* started with internationalization support.
* The logic to handle the web of trust is now implemented. It is
has some bugs; but I'm going to change the algorithm anyway.
It works by calculating the trustlevel on the fly. It may ask
you to provide trust parameters if the calculated trust probability
is too low. I will write a paper which discusses this new approach.
* a couple of changes to the configure script.
* New option "--quick-random" which uses a much quicker random
number generator. Keys generated while this option is in effect
are flags with "INSECURE!" in the user-id. This is a development
only option.
* Read support for new version packets (OpenPGP).
* Comment packets are now of correct OpenPGP type 16. Old comment
packets written by G10 are detected because they always start with
a hash which is an invalid version byte.
* The string "(INSECURE!)" is appended to a new user-id if this
is generated on a system without a good random number generator.
Version 0.2.2 (1998-02-09)
Version 0.2.1 (1998-01-28)
Version 0.2.0 (1998-01-25)
Version 0.1.3 (1998-01-12)
Version 0.1.2 (1998-01-07)
Version 0.1.1 (1998-01-07)
Version 0.1.0 (1998-01-05)
Version 0.0.0 (1997-12-20)
Copyright (C) 1998-2017 Free Software Foundation, Inc.
Copyright (C) 1997-2017 Werner Koch
This file is free software; as a special exception the author gives
unlimited permission to copy and/or distribute it, with or without
modifications, as long as this notice is preserved.
This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/agent/agent.h b/agent/agent.h
index 18d60fb36..eb819a0ff 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -1,642 +1,642 @@
/* agent.h - Global definitions for the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AGENT_H
#define AGENT_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
#include <gpg-error.h>
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include <errno.h>
#include <gcrypt.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.h"
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
#ifndef GCRY_MODULE_ID_USER
#define GCRY_MODULE_ID_USER 1024
#endif
#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
/* Maximum length of a digest. */
#define MAX_DIGEST_LEN 64
/* The maximum length of a passphrase (in bytes). Note: this is
- further contrained by the Assuan line length (and any other text on
+ further constrained by the Assuan line length (and any other text on
the same line). However, the Assuan line length is 1k bytes so
this shouldn't be a problem in practice. */
#define MAX_PASSPHRASE_LEN 255
/* A large struct name "opt" to keep global flags */
EXTERN_UNLESS_MAIN_MODULE
struct
{
unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
int verbose; /* Verbosity level */
int quiet; /* Be as quiet as possible */
int dry_run; /* Don't change any persistent data */
int batch; /* Batch mode */
/* True if we handle sigusr2. */
int sigusr2_enabled;
/* Environment settings gathered at program start or changed using the
Assuan command UPDATESTARTUPTTY. */
session_env_t startup_env;
char *startup_lc_ctype;
char *startup_lc_messages;
/* Enable pinentry debugging (--debug 1024 should also be used). */
int debug_pinentry;
/* Filename of the program to start as pinentry. */
const char *pinentry_program;
/* Filename of the program to handle smartcard tasks. */
const char *scdaemon_program;
int disable_scdaemon; /* Never use the SCdaemon. */
int no_grab; /* Don't let the pinentry grab the keyboard */
/* The name of the file pinentry shall touch before exiting. If
this is not set the file name of the standard socket is used. */
const char *pinentry_touch_file;
/* A string where the first character is used by the pinentry as a
custom invisible character. */
char *pinentry_invisible_char;
/* The timeout value for the Pinentry in seconds. This is passed to
the pinentry if it is not 0. It is up to the pinentry to act
upon this timeout value. */
unsigned long pinentry_timeout;
/* The default and maximum TTL of cache entries. */
unsigned long def_cache_ttl; /* Default. */
unsigned long def_cache_ttl_ssh; /* for SSH. */
unsigned long max_cache_ttl; /* Default. */
unsigned long max_cache_ttl_ssh; /* for SSH. */
/* Flag disallowing bypassing of the warning. */
int enforce_passphrase_constraints;
/* The require minmum length of a passphrase. */
unsigned int min_passphrase_len;
/* The minimum number of non-alpha characters in a passphrase. */
unsigned int min_passphrase_nonalpha;
/* File name with a patternfile or NULL if not enabled. */
const char *check_passphrase_pattern;
/* If not 0 the user is asked to change his passphrase after these
number of days. */
unsigned int max_passphrase_days;
/* If set, a passphrase history will be written and checked at each
passphrase change. */
int enable_passphrase_history;
/* If set the extended key format is used for new keys. Note that
- * this may vave the value 2 in which case
+ * this may have the value 2 in which case
* --disable-extended-key-format won't have any effect and thus
* effectivley locking it. This is required to support existing
* profiles which lock the use of --enable-extended-key-format. */
int enable_extended_key_format;
int running_detached; /* We are running detached from the tty. */
/* If this global option is true, the passphrase cache is ignored
for signing operations. */
int ignore_cache_for_signing;
/* If this global option is true, the user is allowed to
interactively mark certificate in trustlist.txt as trusted. */
int allow_mark_trusted;
/* If this global option is true, the Assuan command
PRESET_PASSPHRASE is allowed. */
int allow_preset_passphrase;
/* If this global option is true, the Assuan option
pinentry-mode=loopback is allowed. */
int allow_loopback_pinentry;
/* Allow the use of an external password cache. If this option is
enabled (which is the default) we send an option to Pinentry
to allow it to enable such a cache. */
int allow_external_cache;
/* If this global option is true, the Assuan option of Pinentry
allow-emacs-prompt is allowed. */
int allow_emacs_pinentry;
int keep_tty; /* Don't switch the TTY (for pinentry) on request */
int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
/* This global option indicates the use of an extra socket. Note
that we use a hack for cleanup handling in gpg-agent.c: If the
value is less than 2 the name has not yet been malloced. */
int extra_socket;
/* This global option indicates the use of an extra socket for web
browsers. Note that we use a hack for cleanup handling in
gpg-agent.c: If the value is less than 2 the name has not yet
been malloced. */
int browser_socket;
/* The digest algorithm to use for ssh fingerprints when
* communicating with the user. */
int ssh_fingerprint_digest;
/* The value of the option --s2k-count. If this option is not given
* or 0 an auto-calibrated value is used. */
unsigned long s2k_count;
} opt;
/* Bit values for the --debug option. */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
/* Test macros for the debug option. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
/* Forward reference for local definitions in command.c. */
struct server_local_s;
/* Declaration of objects from command-ssh.c. */
struct ssh_control_file_s;
typedef struct ssh_control_file_s *ssh_control_file_t;
/* Forward reference for local definitions in call-scd.c. */
struct scd_local_s;
/* Collection of data per session (aka connection). */
struct server_control_s
{
/* Private data used to fire up the connection thread. We use this
structure do avoid an extra allocation for only a few bytes while
spawning a new connection thread. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Flag indicating the connection is run in restricted mode.
A value of 1 if used for --extra-socket,
a value of 2 is used for --browser-socket. */
int restricted;
/* Private data of the server (command.c). */
struct server_local_s *server_local;
/* Private data of the SCdaemon (call-scd.c). */
struct scd_local_s *scd_local;
/* Environment settings for the connection. */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
unsigned long client_pid;
int client_uid;
/* The current pinentry mode. */
pinentry_mode_t pinentry_mode;
/* The TTL used for the --preset option of certain commands. */
int cache_ttl_opt_preset;
/* Information on the currently used digest (for signing commands). */
struct {
int algo;
unsigned char value[MAX_DIGEST_LEN];
int valuelen;
int raw_value: 1;
} digest;
unsigned char keygrip[20];
int have_keygrip;
/* A flag to enable a hack to send the PKAUTH command instead of the
PKSIGN command to the scdaemon. */
int use_auth_call;
/* A flag to inhibit enforced passphrase change during an explicit
passwd command. */
int in_passwd;
/* The current S2K which might be different from the calibrated
count. */
unsigned long s2k_count;
/* If pinentry is active for this thread. It can be more than 1,
when pinentry is called recursively. */
int pinentry_active;
};
/* Status of pinentry. */
enum
{
PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9
};
/* Information pertaining to pinentry requests. */
struct pin_entry_info_s
{
int min_digits; /* min. number of digits required or 0 for freeform entry */
int max_digits; /* max. number of allowed digits allowed*/
int max_tries; /* max. number of allowed tries. */
int failed_tries; /* Number of tries so far failed. */
int with_qualitybar; /* Set if the quality bar should be displayed. */
int with_repeat; /* Request repetition of the passphrase. */
int repeat_okay; /* Repetition worked. */
unsigned int status; /* Status. */
gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check
the PIN */
void *check_cb_arg; /* optional argument which might be of use in the CB */
const char *cb_errtext; /* used by the cb to display a specific error */
size_t max_length; /* Allocated length of the buffer PIN. */
char pin[1]; /* The buffer to hold the PIN or passphrase.
It's actual allocated length is given by
MAX_LENGTH (above). */
};
/* Types of the private keys. */
enum
{
PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */
PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */
PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */
PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard
based key. */
PROTECTED_SHARED_SECRET = 4, /* RFU. */
PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */
};
/* Values for the cache_mode arguments. */
typedef enum
{
CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */
CACHE_MODE_ANY, /* Any mode except ignore and data matches. */
CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */
CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */
CACHE_MODE_SSH, /* SSH related cache. */
CACHE_MODE_NONCE, /* This is a non-predictable nonce. */
CACHE_MODE_PIN, /* PINs stored/retrieved by scdaemon. */
CACHE_MODE_DATA /* Arbitrary data. */
}
cache_mode_t;
/* The TTL is seconds used for adding a new nonce mode cache item. */
#define CACHE_TTL_NONCE 120
/* The TTL in seconds used by the --preset option of some commands.
This is the default value changeable by an OPTION command. */
#define CACHE_TTL_OPT_PRESET 900
/* The type of a function to lookup a TTL by a keygrip. */
typedef int (*lookup_ttl_t)(const char *hexgrip);
/* This is a special version of the usual _() gettext macro. It
assumes a server connection control variable with the name "ctrl"
and uses that to translate a string according to the locale set for
the connection. The macro LunderscoreIMPL is used by i18n to
actually define the inline function when needed. */
#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
#define L_(a) agent_Lunderscore (ctrl, (a))
#define LunderscorePROTO \
static inline const char *agent_Lunderscore (ctrl_t ctrl, \
const char *string) \
GNUPG_GCC_ATTR_FORMAT_ARG(2);
#define LunderscoreIMPL \
static inline const char * \
agent_Lunderscore (ctrl_t ctrl, const char *string) \
{ \
return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
/* */: gettext (string); \
}
#else
#define L_(a) (a)
#endif
/* Information from scdaemon for card keys. */
struct card_key_info_s
{
struct card_key_info_s *next;
char keygrip[41];
char *serialno;
char *idstr;
};
/*-- gpg-agent.c --*/
void agent_exit (int rc)
GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl);
gpg_error_t agent_copy_startup_env (ctrl_t ctrl);
const char *get_agent_socket_name (void);
const char *get_agent_ssh_socket_name (void);
int get_agent_active_connection_count (void);
#ifdef HAVE_W32_SYSTEM
void *get_agent_scd_notify_event (void);
#endif
void agent_sighup_action (void);
int map_pk_openpgp_to_gcry (int openpgp_algo);
/*-- command.c --*/
gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid,
const char *extra);
gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
GPGRT_ATTR_SENTINEL(0);
gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
const char *format, ...)
GPGRT_ATTR_PRINTF(3,4);
void bump_key_eventcounter (void);
void bump_card_eventcounter (void);
void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length);
gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok);
#ifdef HAVE_W32_SYSTEM
int serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen);
#endif /*HAVE_W32_SYSTEM*/
/*-- command-ssh.c --*/
ssh_control_file_t ssh_open_control_file (void);
void ssh_close_control_file (ssh_control_file_t cf);
gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip, int *r_disabled,
int *r_ttl, int *r_confirm);
gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled,
int *r_ttl, int *r_confirm);
void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
/*-- findkey.c --*/
gpg_error_t agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result);
int agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force,
const char *serialno, const char *keyref);
gpg_error_t agent_key_from_file (ctrl_t ctrl,
const char *cache_nonce,
const char *desc_text,
const unsigned char *grip,
unsigned char **shadow_info,
cache_mode_t cache_mode,
lookup_ttl_t lookup_ttl,
gcry_sexp_t *result,
char **r_passphrase);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
int agent_is_dsa_key (gcry_sexp_t s_key);
int agent_is_eddsa_key (gcry_sexp_t s_key);
int agent_key_available (const unsigned char *grip);
gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype,
unsigned char **r_shadow_info);
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
int force, int only_stubs);
/*-- call-pinentry.c --*/
void initialize_module_call_pinentry (void);
void agent_query_dump_state (void);
void agent_reset_query (ctrl_t ctrl);
int pinentry_active_p (ctrl_t ctrl, int waitseconds);
gpg_error_t agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *inital_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_passphrase (ctrl_t ctrl, char **retpass,
const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
const char *notokay, int with_cancel);
int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
int agent_popup_message_start (ctrl_t ctrl,
const char *desc, const char *ok_btn);
void agent_popup_message_stop (ctrl_t ctrl);
int agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode);
/*-- cache.c --*/
void initialize_module_cache (void);
void deinitialize_module_cache (void);
void agent_cache_housekeeping (void);
void agent_flush_cache (int pincache_only);
int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl);
char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode);
void agent_store_cache_hit (const char *key);
/*-- pksign.c --*/
gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen);
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode);
/*-- pkdecrypt.c --*/
int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding);
/*-- genkey.c --*/
int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
char **failed_constraint);
gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase);
int agent_genkey (ctrl_t ctrl, const char *cache_nonce,
const char *keyparam, size_t keyparmlen,
int no_protection, const char *override_passphrase,
int preset, membuf_t *outbuf);
gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr);
/*-- protect.c --*/
void set_s2k_calibration_time (unsigned int milliseconds);
unsigned long get_calibrated_s2k_count (void);
unsigned long get_standard_s2k_count (void);
unsigned char get_standard_s2k_count_rfc4880 (void);
unsigned long get_standard_s2k_time (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count, int use_ocb);
gpg_error_t agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen);
int agent_private_key_type (const unsigned char *privatekey);
unsigned char *make_shadow_info (const char *serialno, const char *idstring);
int agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result);
gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen);
gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen);
gpg_error_t agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force);
/*-- trustlist.c --*/
void initialize_module_trustlist (void);
gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled);
gpg_error_t agent_listtrusted (void *assuan_context);
gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name,
const char *fpr, int flag);
void agent_reload_trustlist (void);
/*-- divert-scd.c --*/
int divert_pksign (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen);
int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding);
int divert_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen);
/*-- call-scd.c --*/
void initialize_module_call_scd (void);
void agent_scd_dump_state (void);
int agent_scd_check_running (void);
int agent_reset_scd (ctrl_t ctrl);
int agent_card_learn (ctrl_t ctrl,
void (*kpinfo_cb)(void*, const char *),
void *kpinfo_cb_arg,
void (*certinfo_cb)(void*, const char *),
void *certinfo_cb_arg,
void (*sinfo_cb)(void*, const char *,
size_t, const char *),
void *sinfo_cb_arg);
int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand);
int agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
int mdalgo,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen);
int agent_card_pkdecrypt (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*,size_t),
void *getpin_cb_arg,
const char *desc_text,
const unsigned char *indata, size_t indatalen,
char **r_buf, size_t *r_buflen, int *r_padding);
int agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen);
int agent_card_readkey (ctrl_t ctrl, const char *id,
unsigned char **r_buf, char **r_keyref);
gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result,
const char *keygrip);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context);
void agent_card_free_keyinfo (struct card_key_info_s *l);
gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip,
int cap, struct card_key_info_s **result);
void agent_card_killscd (void);
/*-- learncard.c --*/
int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force);
/*-- cvt-openpgp.c --*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_format,
gcry_mpi_t *mpi_array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags);
#endif /*AGENT_H*/
diff --git a/agent/cache.c b/agent/cache.c
index c5c67e320..20a604006 100644
--- a/agent/cache.c
+++ b/agent/cache.c
@@ -1,571 +1,571 @@
/* cache.c - keep a cache of passphrases
* Copyright (C) 2002, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <npth.h>
#include "agent.h"
/* The default TTL for DATA items. This has no configure
* option because it is expected that clients provide a TTL. */
#define DEF_CACHE_TTL_DATA (10 * 60) /* 10 minutes. */
/* The size of the encryption key in bytes. */
#define ENCRYPTION_KEYSIZE (128/8)
/* A mutex used to serialize access to the cache. */
static npth_mutex_t cache_lock;
/* The encryption context. This is the only place where the
encryption key for all cached entries is available. It would be nice
to keep this (or just the key) in some hardware device, for example
a TPM. Libgcrypt could be extended to provide such a service.
With the current scheme it is easy to retrieve the cached entries
if access to Libgcrypt's memory is available. The encryption
merely avoids grepping for clear texts in the memory. Nevertheless
the encryption provides the necessary infrastructure to make it
more secure. */
static gcry_cipher_hd_t encryption_handle;
struct secret_data_s {
int totallen; /* This includes the padding and space for AESWRAP. */
char data[1]; /* A string. */
};
/* The cache object. */
typedef struct cache_item_s *ITEM;
struct cache_item_s {
ITEM next;
time_t created;
time_t accessed; /* Not updated for CACHE_MODE_DATA */
int ttl; /* max. lifetime given in seconds, -1 one means infinite */
struct secret_data_s *pw;
cache_mode_t cache_mode;
int restricted; /* The value of ctrl->restricted is part of the key. */
char key[1];
};
/* The cache himself. */
static ITEM thecache;
/* NULL or the last cache key stored by agent_store_cache_hit. */
static char *last_stored_cache_key;
/* This function must be called once to initialize this module. It
has to be done before a second thread is spawned. */
void
initialize_module_cache (void)
{
int err;
err = npth_mutex_init (&cache_lock, NULL);
if (err)
log_fatal ("error initializing cache module: %s\n", strerror (err));
}
void
deinitialize_module_cache (void)
{
gcry_cipher_close (encryption_handle);
encryption_handle = NULL;
}
/* We do the encryption init on the fly. We can't do it in the module
init code because that is run before we listen for connections and
in case we are started on demand by gpg etc. it will only wait for
a few seconds to decide whether the agent may now accept
connections. Thus we should get into listen state as soon as
possible. */
static gpg_error_t
init_encryption (void)
{
gpg_error_t err;
void *key;
if (encryption_handle)
return 0; /* Shortcut - Already initialized. */
err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE);
if (!err)
{
key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM);
if (!key)
err = gpg_error_from_syserror ();
else
{
err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE);
xfree (key);
}
if (err)
{
gcry_cipher_close (encryption_handle);
encryption_handle = NULL;
}
}
if (err)
log_error ("error initializing cache encryption context: %s\n",
gpg_strerror (err));
return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0;
}
static void
release_data (struct secret_data_s *data)
{
xfree (data);
}
static gpg_error_t
new_data (const char *string, struct secret_data_s **r_data)
{
gpg_error_t err;
struct secret_data_s *d, *d_enc;
size_t length;
int total;
*r_data = NULL;
err = init_encryption ();
if (err)
return err;
length = strlen (string) + 1;
/* We pad the data to 32 bytes so that it get more complicated
finding something out by watching allocation patterns. This is
usually not possible but we better assume nothing about our secure
storage provider. To support the AESWRAP mode we need to add 8
extra bytes as well. */
total = (length + 8) + 32 - ((length+8) % 32);
d = xtrymalloc_secure (sizeof *d + total - 1);
if (!d)
return gpg_error_from_syserror ();
memcpy (d->data, string, length);
d_enc = xtrymalloc (sizeof *d_enc + total - 1);
if (!d_enc)
{
err = gpg_error_from_syserror ();
xfree (d);
return err;
}
d_enc->totallen = total;
err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total,
d->data, total - 8);
xfree (d);
if (err)
{
xfree (d_enc);
return err;
}
*r_data = d_enc;
return 0;
}
/* Check whether there are items to expire. */
static void
housekeeping (void)
{
ITEM r, rprev;
time_t current = gnupg_get_time ();
/* First expire the actual data */
for (r=thecache; r; r = r->next)
{
if (r->cache_mode == CACHE_MODE_PIN)
- ; /* Don't let it expire - scdaemon explictly flushes them. */
+ ; /* Don't let it expire - scdaemon explicitly flushes them. */
else if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current)
{
if (DBG_CACHE)
log_debug (" expired '%s'.%d (%ds after last access)\n",
r->key, r->restricted, r->ttl);
release_data (r->pw);
r->pw = NULL;
r->accessed = current;
}
}
/* Second, make sure that we also remove them based on the created
* stamp so that the user has to enter it from time to time. We
* don't do this for data items which are used to storage secrets in
* meory and are not user entered passphrases etc. */
for (r=thecache; r; r = r->next)
{
unsigned long maxttl;
switch (r->cache_mode)
{
case CACHE_MODE_DATA:
case CACHE_MODE_PIN:
continue; /* No MAX TTL here. */
case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break;
default: maxttl = opt.max_cache_ttl; break;
}
if (r->pw && r->created + maxttl < current)
{
if (DBG_CACHE)
log_debug (" expired '%s'.%d (%lus after creation)\n",
r->key, r->restricted, opt.max_cache_ttl);
release_data (r->pw);
r->pw = NULL;
r->accessed = current;
}
}
/* Third, make sure that we don't have too many items in the list.
* Expire old and unused entries after 30 minutes. */
for (rprev=NULL, r=thecache; r; )
{
if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current)
{
ITEM r2 = r->next;
if (DBG_CACHE)
log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n",
r->key, r->restricted, r->cache_mode);
xfree (r);
if (!rprev)
thecache = r2;
else
rprev->next = r2;
r = r2;
}
else
{
rprev = r;
r = r->next;
}
}
}
void
agent_cache_housekeeping (void)
{
int res;
if (DBG_CACHE)
log_debug ("agent_cache_housekeeping\n");
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
housekeeping ();
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
}
void
agent_flush_cache (int pincache_only)
{
ITEM r;
int res;
if (DBG_CACHE)
log_debug ("agent_flush_cache%s\n", pincache_only?" (pincache only)":"");
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
for (r=thecache; r; r = r->next)
{
if (pincache_only && r->cache_mode != CACHE_MODE_PIN)
continue;
if (r->pw)
{
if (DBG_CACHE)
log_debug (" flushing '%s'.%d\n", r->key, r->restricted);
release_data (r->pw);
r->pw = NULL;
r->accessed = 0;
}
}
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
}
/* Compare two cache modes. */
static int
cache_mode_equal (cache_mode_t a, cache_mode_t b)
{
/* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */
return ((a == CACHE_MODE_ANY
&& !(b == CACHE_MODE_IGNORE || b == CACHE_MODE_DATA))
|| (b == CACHE_MODE_ANY
&& !(a == CACHE_MODE_IGNORE || a == CACHE_MODE_DATA))
|| a == b);
}
/* Store the string DATA in the cache under KEY and mark it with a
maximum lifetime of TTL seconds. If there is already data under
this key, it will be replaced. Using a DATA of NULL deletes the
entry. A TTL of 0 is replaced by the default TTL and a TTL of -1
set infinite timeout. CACHE_MODE is stored with the cache entry
and used to select different timeouts. */
int
agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl)
{
gpg_error_t err = 0;
ITEM r;
int res;
int restricted = ctrl? ctrl->restricted : -1;
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
if (DBG_CACHE)
log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n",
key, restricted, cache_mode, ttl);
housekeeping ();
if (!ttl)
{
switch(cache_mode)
{
case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break;
case CACHE_MODE_DATA: ttl = DEF_CACHE_TTL_DATA; break;
case CACHE_MODE_PIN: ttl = -1; break;
default: ttl = opt.def_cache_ttl; break;
}
}
if ((!ttl && data) || cache_mode == CACHE_MODE_IGNORE)
goto out;
for (r=thecache; r; r = r->next)
{
if (cache_mode == CACHE_MODE_PIN && data)
{
/* PIN mode is special because it is only used by scdaemon. */
if (!strcmp (r->key, key))
break;
}
else if (cache_mode == CACHE_MODE_PIN)
{
/* FIXME: Parse the structure of the key and delete several
* cached PINS. */
if (!strcmp (r->key, key))
break;
}
else if (((cache_mode != CACHE_MODE_USER
&& cache_mode != CACHE_MODE_NONCE)
|| cache_mode_equal (r->cache_mode, cache_mode))
&& r->restricted == restricted
&& !strcmp (r->key, key))
break;
}
if (r) /* Replace. */
{
if (r->pw)
{
release_data (r->pw);
r->pw = NULL;
}
if (data)
{
r->created = r->accessed = gnupg_get_time ();
r->ttl = ttl;
r->cache_mode = cache_mode;
err = new_data (data, &r->pw);
if (err)
log_error ("error replacing cache item: %s\n", gpg_strerror (err));
}
}
else if (data) /* Insert. */
{
r = xtrycalloc (1, sizeof *r + strlen (key));
if (!r)
err = gpg_error_from_syserror ();
else
{
strcpy (r->key, key);
r->restricted = restricted;
r->created = r->accessed = gnupg_get_time ();
r->ttl = ttl;
r->cache_mode = cache_mode;
err = new_data (data, &r->pw);
if (err)
xfree (r);
else
{
r->next = thecache;
thecache = r;
}
}
if (err)
log_error ("error inserting cache item: %s\n", gpg_strerror (err));
}
out:
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
return err;
}
/* Try to find an item in the cache. Returns NULL if not found or an
* malloced string with the value. */
char *
agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode)
{
gpg_error_t err;
ITEM r;
char *value = NULL;
int res;
int last_stored = 0;
int restricted = ctrl? ctrl->restricted : -1;
int yes;
if (cache_mode == CACHE_MODE_IGNORE)
return NULL;
res = npth_mutex_lock (&cache_lock);
if (res)
log_fatal ("failed to acquire cache mutex: %s\n", strerror (res));
if (!key)
{
key = last_stored_cache_key;
if (!key)
goto out;
last_stored = 1;
}
if (DBG_CACHE)
log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n",
key, ctrl->restricted, cache_mode,
last_stored? " (stored cache key)":"");
housekeeping ();
for (r=thecache; r; r = r->next)
{
if (cache_mode == CACHE_MODE_PIN)
yes = (r->pw && !strcmp (r->key, key));
else if (r->pw
&& ((cache_mode != CACHE_MODE_USER
&& cache_mode != CACHE_MODE_NONCE)
|| cache_mode_equal (r->cache_mode, cache_mode))
&& r->restricted == restricted
&& !strcmp (r->key, key))
yes = 1;
else
yes = 0;
if (yes)
{
/* Note: To avoid races KEY may not be accessed anymore
* below. Note also that we don't update the accessed time
* for data items. */
if (r->cache_mode != CACHE_MODE_DATA)
r->accessed = gnupg_get_time ();
if (DBG_CACHE)
log_debug ("... hit\n");
if (r->pw->totallen < 32)
err = gpg_error (GPG_ERR_INV_LENGTH);
else if ((err = init_encryption ()))
;
else if (!(value = xtrymalloc_secure (r->pw->totallen - 8)))
err = gpg_error_from_syserror ();
else
{
err = gcry_cipher_decrypt (encryption_handle,
value, r->pw->totallen - 8,
r->pw->data, r->pw->totallen);
}
if (err)
{
xfree (value);
value = NULL;
log_error ("retrieving cache entry '%s'.%d failed: %s\n",
key, restricted, gpg_strerror (err));
}
break;
}
}
if (DBG_CACHE && value == NULL)
log_debug ("... miss\n");
out:
res = npth_mutex_unlock (&cache_lock);
if (res)
log_fatal ("failed to release cache mutex: %s\n", strerror (res));
return value;
}
/* Store the key for the last successful cache hit. That value is
used by agent_get_cache if the requested KEY is given as NULL.
NULL may be used to remove that key. */
void
agent_store_cache_hit (const char *key)
{
char *new;
char *old;
/* To make sure the update is atomic under the non-preemptive thread
* model, we must make sure not to surrender control to a different
* thread. Therefore, we avoid calling the allocator during the
* update.
*
* Background: xtrystrdup uses gcry_strdup which may use the secure
* memory allocator of Libgcrypt. That allocator takes locks and
* since version 1.14 libgpg-error is nPth aware and thus taking a
* lock may now lead to thread switch. Note that this only happens
* when secure memory is _allocated_ (the standard allocator uses
* malloc which is not nPth aware) but not when calling _xfree_
* because gcry_free needs to check whether the pointer is in secure
* memory and thus needs to take a lock.
*/
new = key ? xtrystrdup (key) : NULL;
/* Atomic update. */
old = last_stored_cache_key;
last_stored_cache_key = new;
/* Done. */
xfree (old);
}
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index 8adbe01cd..51f37e18b 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -1,3785 +1,3785 @@
/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
* Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
* Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs
are:
RFC-4250 - Protocol Assigned Numbers
RFC-4251 - Protocol Architecture
RFC-4252 - Authentication Protocol
RFC-4253 - Transport Layer Protocol
RFC-5656 - ECC support
The protocol for the agent is defined in:
https://tools.ietf.org/html/draft-miller-ssh-agent
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SYS_UCRED_H
#include <sys/ucred.h>
#endif
#ifdef HAVE_UCRED_H
#include <ucred.h>
#endif
#include "agent.h"
#include "../common/i18n.h"
#include "../common/util.h"
#include "../common/ssh-utils.h"
/* Request types. */
#define SSH_REQUEST_REQUEST_IDENTITIES 11
#define SSH_REQUEST_SIGN_REQUEST 13
#define SSH_REQUEST_ADD_IDENTITY 17
#define SSH_REQUEST_REMOVE_IDENTITY 18
#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19
#define SSH_REQUEST_LOCK 22
#define SSH_REQUEST_UNLOCK 23
#define SSH_REQUEST_ADD_ID_CONSTRAINED 25
/* Options. */
#define SSH_OPT_CONSTRAIN_LIFETIME 1
#define SSH_OPT_CONSTRAIN_CONFIRM 2
/* Response types. */
#define SSH_RESPONSE_SUCCESS 6
#define SSH_RESPONSE_FAILURE 5
#define SSH_RESPONSE_IDENTITIES_ANSWER 12
#define SSH_RESPONSE_SIGN_RESPONSE 14
/* Other constants. */
#define SSH_DSA_SIGNATURE_PADDING 20
#define SSH_DSA_SIGNATURE_ELEMS 2
#define SSH_AGENT_RSA_SHA2_256 0x02
#define SSH_AGENT_RSA_SHA2_512 0x04
#define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
#define SPEC_FLAG_IS_ECDSA (1 << 1)
#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/
#define SPEC_FLAG_WITH_CERT (1 << 7)
/* The name of the control file. */
#define SSH_CONTROL_FILE_NAME "sshcontrol"
/* The blurb we put into the header of a newly created control file. */
static const char sshcontrolblurb[] =
"# List of allowed ssh keys. Only keys present in this file are used\n"
"# in the SSH protocol. The ssh-add tool may add new entries to this\n"
"# file to enable them; you may also add them manually. Comment\n"
"# lines, like this one, as well as empty lines are ignored. Lines do\n"
"# have a certain length limit but this is not serious limitation as\n"
"# the format of the entries is fixed and checked by gpg-agent. A\n"
"# non-comment line starts with optional white spaces, followed by the\n"
"# keygrip of the key given as 40 hex digits, optionally followed by a\n"
"# caching TTL in seconds, and another optional field for arbitrary\n"
"# flags. Prepend the keygrip with an '!' mark to disable it.\n"
"\n";
/* Macros. */
/* Return a new uint32 with b0 being the most significant byte and b3
being the least significant byte. */
#define uint32_construct(b0, b1, b2, b3) \
((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
/*
* Basic types.
*/
/* Type for a request handler. */
typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
estream_t request,
estream_t response);
struct ssh_key_type_spec;
typedef struct ssh_key_type_spec ssh_key_type_spec_t;
/* Type, which is used for associating request handlers with the
appropriate request IDs. */
typedef struct ssh_request_spec
{
unsigned char type;
ssh_request_handler_t handler;
const char *identifier;
unsigned int secret_input;
} ssh_request_spec_t;
/* Type for "key modifier functions", which are necessary since
OpenSSH and GnuPG treat key material slightly different. A key
modifier is called right after a new key identity has been received
in order to "sanitize" the material. */
typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
gcry_mpi_t *mpis);
/* The encoding of a generated signature is dependent on the
algorithm; therefore algorithm specific signature encoding
functions are necessary. */
typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t sig);
/* Type, which is used for boundling all the algorithm specific
information together in a single object. */
struct ssh_key_type_spec
{
/* Algorithm identifier as used by OpenSSH. */
const char *ssh_identifier;
/* Human readable name of the algorithm. */
const char *name;
/* Algorithm identifier as used by GnuPG. */
int algo;
/* List of MPI names for secret keys; order matches the one of the
agent protocol. */
const char *elems_key_secret;
/* List of MPI names for public keys; order matches the one of the
agent protocol. */
const char *elems_key_public;
/* List of MPI names for signature data. */
const char *elems_signature;
/* List of MPI names for secret keys; order matches the one, which
is required by gpg-agent's key access layer. */
const char *elems_sexp_order;
/* Key modifier function. Key modifier functions are necessary in
order to fix any inconsistencies between the representation of
keys on the SSH and on the GnuPG side. */
ssh_key_modifier_t key_modifier;
/* Signature encoder function. Signature encoder functions are
necessary since the encoding of signatures depends on the used
algorithm. */
ssh_signature_encoder_t signature_encoder;
/* The name of the ECC curve or NULL for non-ECC algos. This is the
* canonical name for the curve as specified by RFC-5656. */
const char *curve_name;
/* An alias for curve_name or NULL. Actually this is Libcgrypt's
* primary name of the curve. */
const char *alt_curve_name;
/* The hash algorithm to be used with this key. 0 for using the
default. */
int hash_algo;
/* Misc flags. */
unsigned int flags;
};
/* Definition of an object to access the sshcontrol file. */
struct ssh_control_file_s
{
char *fname; /* Name of the file. */
FILE *fp; /* This is never NULL. */
int lnr; /* The current line number. */
struct {
int valid; /* True if the data of this structure is valid. */
int disabled; /* The item is disabled. */
int ttl; /* The TTL of the item. */
int confirm; /* The confirm flag is set. */
char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */
} item;
};
/* Prototypes. */
static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_lock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
estream_t request,
estream_t response);
static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t signature);
static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
struct peer_info_s
{
unsigned long pid;
int uid;
};
/* Global variables. */
/* Associating request types with the corresponding request
handlers. */
static const ssh_request_spec_t request_specs[] =
{
#define REQUEST_SPEC_DEFINE(id, name, secret_input) \
{ SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1),
REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0),
REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1),
REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1),
REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0),
REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0),
REQUEST_SPEC_DEFINE (LOCK, lock, 0),
REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0)
#undef REQUEST_SPEC_DEFINE
};
/* Table holding key type specifications. */
static const ssh_key_type_spec_t ssh_key_types[] =
{
{
"ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA
},
{
"ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2
},
{
"ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, 0
},
{
"ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
},
{
"ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
},
{
"ssh-ed25519-cert-v01@openssh.com", "Ed25519",
GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_eddsa,
"Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
},
{
"ssh-rsa-cert-v01@openssh.com", "RSA",
GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
},
{
"ssh-dss-cert-v01@openssh.com", "DSA",
GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
NULL, ssh_signature_encoder_dsa,
NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp256", "NIST P-256", GCRY_MD_SHA256,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp384", "NIST P-384", GCRY_MD_SHA384,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
},
{
"ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
GCRY_PK_ECC, "qd", "q", "rs", "qd",
NULL, ssh_signature_encoder_ecdsa,
"nistp521", "NIST P-521", GCRY_MD_SHA512,
SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
}
};
/*
General utility functions.
*/
/* A secure realloc, i.e. it makes sure to allocate secure memory if A
is NULL. This is required because the standard gcry_realloc does
not know whether to allocate secure or normal if NULL is passed as
existing buffer. */
static void *
realloc_secure (void *a, size_t n)
{
void *p;
if (a)
p = gcry_realloc (a, n);
else
p = gcry_malloc_secure (n);
return p;
}
/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
* NULL if not found. If found the ssh indetifier is returned and a
* pointer to the canonical curve name as specified for ssh is stored
* at R_CANON_NAME. */
static const char *
ssh_identifier_from_curve_name (const char *curve_name,
const char **r_canon_name)
{
int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if (ssh_key_types[i].curve_name
&& (!strcmp (ssh_key_types[i].curve_name, curve_name)
|| (ssh_key_types[i].alt_curve_name
&& !strcmp (ssh_key_types[i].alt_curve_name, curve_name))))
{
*r_canon_name = ssh_key_types[i].curve_name;
return ssh_key_types[i].ssh_identifier;
}
return NULL;
}
/*
Primitive I/O functions.
*/
/* Read a byte from STREAM, store it in B. */
static gpg_error_t
stream_read_byte (estream_t stream, unsigned char *b)
{
gpg_error_t err;
int ret;
ret = es_fgetc (stream);
if (ret == EOF)
{
if (es_ferror (stream))
err = gpg_error_from_syserror ();
else
err = gpg_error (GPG_ERR_EOF);
*b = 0;
}
else
{
*b = ret & 0xFF;
err = 0;
}
return err;
}
/* Write the byte contained in B to STREAM. */
static gpg_error_t
stream_write_byte (estream_t stream, unsigned char b)
{
gpg_error_t err;
int ret;
ret = es_fputc (b, stream);
if (ret == EOF)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a uint32 from STREAM, store it in UINT32. */
static gpg_error_t
stream_read_uint32 (estream_t stream, u32 *uint32)
{
unsigned char buffer[4];
size_t bytes_read;
gpg_error_t err;
int ret;
ret = es_read (stream, buffer, sizeof (buffer), &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != sizeof (buffer))
err = gpg_error (GPG_ERR_EOF);
else
{
u32 n;
n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]);
*uint32 = n;
err = 0;
}
}
return err;
}
/* Write the uint32 contained in UINT32 to STREAM. */
static gpg_error_t
stream_write_uint32 (estream_t stream, u32 uint32)
{
unsigned char buffer[4];
gpg_error_t err;
int ret;
buffer[0] = uint32 >> 24;
buffer[1] = uint32 >> 16;
buffer[2] = uint32 >> 8;
buffer[3] = uint32 >> 0;
ret = es_write (stream, buffer, sizeof (buffer), NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read SIZE bytes from STREAM into BUFFER. */
static gpg_error_t
stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
{
gpg_error_t err;
size_t bytes_read;
int ret;
ret = es_read (stream, buffer, size, &bytes_read);
if (ret)
err = gpg_error_from_syserror ();
else
{
if (bytes_read != size)
err = gpg_error (GPG_ERR_EOF);
else
err = 0;
}
return err;
}
/* Skip over SIZE bytes from STREAM. */
static gpg_error_t
stream_read_skip (estream_t stream, size_t size)
{
char buffer[128];
size_t bytes_to_read, bytes_read;
int ret;
do
{
bytes_to_read = size;
if (bytes_to_read > sizeof buffer)
bytes_to_read = sizeof buffer;
ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
if (ret)
return gpg_error_from_syserror ();
else if (bytes_read != bytes_to_read)
return gpg_error (GPG_ERR_EOF);
else
size -= bytes_to_read;
}
while (size);
return 0;
}
/* Write SIZE bytes from BUFFER to STREAM. */
static gpg_error_t
stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
{
gpg_error_t err;
int ret;
ret = es_write (stream, buffer, size, NULL);
if (ret)
err = gpg_error_from_syserror ();
else
err = 0;
return err;
}
/* Read a binary string from STREAM into STRING, store size of string
in STRING_SIZE. Append a hidden nul so that the result may
directly be used as a C string. Depending on SECURE use secure
memory for STRING. If STRING is NULL do only a dummy read. */
static gpg_error_t
stream_read_string (estream_t stream, unsigned int secure,
unsigned char **string, u32 *string_size)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
if (string_size)
*string_size = 0;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto out;
if (string)
{
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length + 1);
else
buffer = xtrymalloc (length + 1);
if (! buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Read data. */
err = stream_read_data (stream, buffer, length);
if (err)
goto out;
/* Finalize string object. */
buffer[length] = 0;
*string = buffer;
}
else /* Dummy read requested. */
{
err = stream_read_skip (stream, length);
if (err)
goto out;
}
if (string_size)
*string_size = length;
out:
if (err)
xfree (buffer);
return err;
}
/* Read a binary string from STREAM and store it as an opaque MPI at
R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
Depending on SECURE use secure memory. If the string is too large
for key material return an error. */
static gpg_error_t
stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
{
gpg_error_t err;
unsigned char *buffer = NULL;
u32 length = 0;
*r_mpi = NULL;
/* Read string length. */
err = stream_read_uint32 (stream, &length);
if (err)
goto leave;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (length > (4096/8) + 8)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto leave;
}
/* Allocate space. */
if (secure)
buffer = xtrymalloc_secure (length+1);
else
buffer = xtrymalloc (length+1);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Read data. */
err = stream_read_data (stream, buffer + 1, length);
if (err)
goto leave;
buffer[0] = 0x40;
*r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
buffer = NULL;
leave:
xfree (buffer);
return err;
}
/* Read a C-string from STREAM, store copy in STRING. */
static gpg_error_t
stream_read_cstring (estream_t stream, char **string)
{
return stream_read_string (stream, 0, (unsigned char **)string, NULL);
}
/* Write a binary string from STRING of size STRING_N to STREAM. */
static gpg_error_t
stream_write_string (estream_t stream,
const unsigned char *string, u32 string_n)
{
gpg_error_t err;
err = stream_write_uint32 (stream, string_n);
if (err)
goto out;
err = stream_write_data (stream, string, string_n);
out:
return err;
}
/* Write a C-string from STRING to STREAM. */
static gpg_error_t
stream_write_cstring (estream_t stream, const char *string)
{
gpg_error_t err;
err = stream_write_string (stream,
(const unsigned char *) string, strlen (string));
return err;
}
/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE
use secure memory. */
static gpg_error_t
stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint)
{
unsigned char *mpi_data;
u32 mpi_data_size;
gpg_error_t err;
gcry_mpi_t mpi;
mpi_data = NULL;
err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size);
if (err)
goto out;
/* To avoid excessive use of secure memory we check that an MPI is
not too large. */
if (mpi_data_size > 520)
{
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
err = GPG_ERR_TOO_LARGE;
goto out;
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL);
if (err)
goto out;
*mpint = mpi;
out:
xfree (mpi_data);
return err;
}
/* Write the MPI contained in MPINT to STREAM. */
static gpg_error_t
stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
{
unsigned char *mpi_buffer;
size_t mpi_buffer_n;
gpg_error_t err;
mpi_buffer = NULL;
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint);
if (err)
goto out;
err = stream_write_string (stream, mpi_buffer, mpi_buffer_n);
out:
xfree (mpi_buffer);
return err;
}
/* Copy data from SRC to DST until EOF is reached. */
static gpg_error_t
stream_copy (estream_t dst, estream_t src)
{
char buffer[BUFSIZ];
size_t bytes_read;
gpg_error_t err;
int ret;
err = 0;
while (1)
{
ret = es_read (src, buffer, sizeof (buffer), &bytes_read);
if (ret || (! bytes_read))
{
if (ret)
err = gpg_error_from_syserror ();
break;
}
ret = es_write (dst, buffer, bytes_read, NULL);
if (ret)
{
err = gpg_error_from_syserror ();
break;
}
}
return err;
}
/* Open the ssh control file and create it if not available. With
APPEND passed as true the file will be opened in append mode,
otherwise in read only mode. On success 0 is returned and a new
control file object stored at R_CF. On error an error code is
returned and NULL is stored at R_CF. */
static gpg_error_t
open_control_file (ssh_control_file_t *r_cf, int append)
{
gpg_error_t err;
ssh_control_file_t cf;
cf = xtrycalloc (1, sizeof *cf);
if (!cf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Note: As soon as we start to use non blocking functions here
(i.e. where Pth might switch threads) we need to employ a
mutex. */
cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
if (!cf->fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: With "a+" we are not able to check whether this will
be created and thus the blurb needs to be written first. */
cf->fp = fopen (cf->fname, append? "a+":"r");
if (!cf->fp && errno == ENOENT)
{
estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
es_fputs (sshcontrolblurb, stream);
es_fclose (stream);
cf->fp = fopen (cf->fname, append? "a+":"r");
}
if (!cf->fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
cf->fname, gpg_strerror (err));
goto leave;
}
err = 0;
leave:
if (err && cf)
{
if (cf->fp)
fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
else
*r_cf = cf;
return err;
}
static void
rewind_control_file (ssh_control_file_t cf)
{
fseek (cf->fp, 0, SEEK_SET);
cf->lnr = 0;
clearerr (cf->fp);
}
static void
close_control_file (ssh_control_file_t cf)
{
if (!cf)
return;
fclose (cf->fp);
xfree (cf->fname);
xfree (cf);
}
/* Read the next line from the control file and store the data in CF.
Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
static gpg_error_t
read_control_file_item (ssh_control_file_t cf)
{
int c, i, n;
char *p, *pend, line[256];
long ttl = 0;
cf->item.valid = 0;
clearerr (cf->fp);
do
{
if (!fgets (line, DIM(line)-1, cf->fp) )
{
if (feof (cf->fp))
return gpg_error (GPG_ERR_EOF);
return gpg_error_from_syserror ();
}
cf->lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line */
while ( (c=getc (cf->fp)) != EOF && c != '\n')
;
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
cf->item.disabled = 0;
if (*p == '!')
{
cf->item.disabled = 1;
for (p++; spacep (p); p++)
;
}
for (i=0; hexdigitp (p) && i < 40; p++, i++)
cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
cf->item.hexgrip[i] = 0;
if (i != 40 || !(spacep (p) || *p == '\n'))
{
log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
ttl = strtol (p, &pend, 10);
p = pend;
if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
{
log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
cf->item.ttl = 0;
}
cf->item.ttl = ttl;
/* Now check for key-value pairs of the form NAME[=VALUE]. */
cf->item.confirm = 0;
while (*p)
{
for (; spacep (p) && *p != '\n'; p++)
;
if (!*p || *p == '\n')
break;
n = strcspn (p, "= \t\n");
if (p[n] == '=')
{
log_error ("%s:%d: assigning a value to a flag is not yet supported; "
"flag ignored\n", cf->fname, cf->lnr);
p++;
}
else if (n == 7 && !memcmp (p, "confirm", 7))
{
cf->item.confirm = 1;
}
else
log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
cf->fname, cf->lnr, n, p);
p += n;
}
/* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
/* cf->fname, cf->lnr, */
/* cf->item.hexgrip, cf->item.ttl, */
/* cf->item.disabled? " disabled":"", */
/* cf->item.confirm? " confirm":""); */
cf->item.valid = 1;
return 0; /* Okay: valid entry found. */
}
/* Search the control file CF from the beginning until a matching
HEXGRIP is found; return success in this case and store true at
DISABLED if the found key has been disabled. If R_TTL is not NULL
a specified TTL for that key is stored there. If R_CONFIRM is not
NULL it is set to 1 if the key has the confirm flag set. */
static gpg_error_t
search_control_file (ssh_control_file_t cf, const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
log_assert (strlen (hexgrip) == 40 );
if (r_disabled)
*r_disabled = 0;
if (r_ttl)
*r_ttl = 0;
if (r_confirm)
*r_confirm = 0;
rewind_control_file (cf);
while (!(err=read_control_file_item (cf)))
{
if (!cf->item.valid)
continue; /* Should not happen. */
if (!strcmp (hexgrip, cf->item.hexgrip))
break;
}
if (!err)
{
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
}
return err;
}
/* Add an entry to the control file to mark the key with the keygrip
HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
for it. FMTFPR is the fingerprint string. This function is in
general used to add a key received through the ssh-add function.
We can assume that the user wants to allow ssh using this key. */
static gpg_error_t
add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const char *hexgrip, gcry_sexp_t key,
int ttl, int confirm)
{
gpg_error_t err;
ssh_control_file_t cf;
int disabled;
char *fpr_md5 = NULL;
char *fpr_sha256 = NULL;
(void)ctrl;
err = open_control_file (&cf, 1);
if (err)
return err;
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
if (err && gpg_err_code(err) == GPG_ERR_EOF)
{
struct tm *tp;
time_t atime = time (NULL);
err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
if (err)
goto out;
/* Not yet in the file - add it. Because the file has been
opened in append mode, we simply need to write to it. */
tp = localtime (&atime);
fprintf (cf->fp,
("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
"# Fingerprints: %s\n"
"# %s\n"
"%s %d%s\n"),
spec->name,
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec,
fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":"");
}
out:
xfree (fpr_md5);
xfree (fpr_sha256);
close_control_file (cf);
return 0;
}
/* Scan the sshcontrol file and return the TTL. */
static int
ttl_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, ttl;
if (!hexgrip || strlen (hexgrip) != 40)
return 0; /* Wrong input: Use global default. */
if (open_control_file (&cf, 0))
return 0; /* Error: Use the global default TTL. */
if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
|| disabled)
ttl = 0; /* Use the global default if not found or disabled. */
close_control_file (cf);
return ttl;
}
/* Scan the sshcontrol file and return the confirm flag. */
static int
confirm_flag_from_sshcontrol (const char *hexgrip)
{
ssh_control_file_t cf;
int disabled, confirm;
if (!hexgrip || strlen (hexgrip) != 40)
return 1; /* Wrong input: Better ask for confirmation. */
if (open_control_file (&cf, 0))
return 1; /* Error: Better ask for confirmation. */
if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
|| disabled)
confirm = 0; /* If not found or disabled, there is no reason to
ask for confirmation. */
close_control_file (cf);
return confirm;
}
/* Open the ssh control file for reading. This is a public version of
open_control_file. The caller must use ssh_close_control_file to
release the returned handle. */
ssh_control_file_t
ssh_open_control_file (void)
{
ssh_control_file_t cf;
/* Then look at all the registered and non-disabled keys. */
if (open_control_file (&cf, 0))
return NULL;
return cf;
}
/* Close an ssh control file handle. This is the public version of
close_control_file. CF may be NULL. */
void
ssh_close_control_file (ssh_control_file_t cf)
{
close_control_file (cf);
}
/* Read the next item from the ssh control file. The function returns
0 if a item was read, GPG_ERR_EOF on eof or another error value.
R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
file; they are only set on success. */
gpg_error_t
ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
do
err = read_control_file_item (cf);
while (!err && !cf->item.valid);
if (!err)
{
if (r_hexgrip)
strcpy (r_hexgrip, cf->item.hexgrip);
if (r_disabled)
*r_disabled = cf->item.disabled;
if (r_ttl)
*r_ttl = cf->item.ttl;
if (r_confirm)
*r_confirm = cf->item.confirm;
}
return err;
}
/* Search for a key with HEXGRIP in sshcontrol and return all
info. */
gpg_error_t
ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled, int *r_ttl, int *r_confirm)
{
gpg_error_t err;
int i;
const char *s;
char uphexgrip[41];
/* We need to make sure that HEXGRIP is all uppercase. The easiest
way to do this and also check its length is by copying to a
second buffer. */
for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
uphexgrip[i] = 0;
if (i != 40)
err = gpg_error (GPG_ERR_INV_LENGTH);
else
err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
return err;
}
/*
MPI lists.
*/
/* Free the list of MPIs MPI_LIST. */
static void
mpint_list_free (gcry_mpi_t *mpi_list)
{
if (mpi_list)
{
unsigned int i;
for (i = 0; mpi_list[i]; i++)
gcry_mpi_release (mpi_list[i]);
xfree (mpi_list);
}
}
/* Receive key material MPIs from STREAM according to KEY_SPEC;
depending on SECRET expect a public key or secret key. CERT is the
certificate blob used if KEY_SPEC indicates the certificate format;
it needs to be positioned to the end of the nonce. The newly
allocated list of MPIs is stored in MPI_LIST. Returns usual error
code. */
static gpg_error_t
ssh_receive_mpint_list (estream_t stream, int secret,
ssh_key_type_spec_t *spec, estream_t cert,
gcry_mpi_t **mpi_list)
{
const char *elems_public;
unsigned int elems_n;
const char *elems;
int elem_is_secret;
gcry_mpi_t *mpis = NULL;
gpg_error_t err = 0;
unsigned int i;
if (secret)
elems = spec->elems_key_secret;
else
elems = spec->elems_key_public;
elems_n = strlen (elems);
elems_public = spec->elems_key_public;
/* Check that either both, CERT and the WITH_CERT flag, are given or
none of them. */
if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
{
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
elem_is_secret = 0;
for (i = 0; i < elems_n; i++)
{
if (secret)
elem_is_secret = !strchr (elems_public, elems[i]);
if (cert && !elem_is_secret)
err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
else
err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
if (err)
goto out;
}
*mpi_list = mpis;
mpis = NULL;
out:
if (err)
mpint_list_free (mpis);
return err;
}
/* Key modifier function for RSA. */
static gpg_error_t
ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
{
gcry_mpi_t p;
gcry_mpi_t q;
gcry_mpi_t u;
if (strcmp (elems, "nedupq"))
/* Modifying only necessary for secret keys. */
goto out;
u = mpis[3];
p = mpis[4];
q = mpis[5];
if (gcry_mpi_cmp (p, q) > 0)
{
/* P shall be smaller then Q! Swap primes. iqmp becomes u. */
gcry_mpi_t tmp;
tmp = mpis[4];
mpis[4] = mpis[5];
mpis[5] = tmp;
}
else
/* U needs to be recomputed. */
gcry_mpi_invm (u, p, q);
out:
return 0;
}
/* Signature encoder function for RSA. */
static gpg_error_t
ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data;
size_t data_n;
gcry_mpi_t s;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* RSA specific */
s = mpis[0];
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
if (err)
goto out;
err = stream_write_string (signature_blob, data, data_n);
xfree (data);
out:
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for DSA. */
static gpg_error_t
ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
estream_t signature_blob,
gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
unsigned char *data = NULL;
size_t data_n;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* DSA specific code. */
- /* FIXME: Why this complicated code? Why collecting boths mpis in a
+ /* FIXME: Why this complicated code? Why collecting both mpis in a
buffer instead of writing them out one after the other? */
for (i = 0; i < 2; i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
if (err)
break;
if (data_n > SSH_DSA_SIGNATURE_PADDING)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0,
SSH_DSA_SIGNATURE_PADDING - data_n);
memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING)
+ (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n);
xfree (data);
data = NULL;
}
if (err)
goto out;
err = stream_write_string (signature_blob, buffer, sizeof (buffer));
out:
xfree (data);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for ECDSA. */
static gpg_error_t
ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
gcry_mpi_t sig_value = NULL;
gcry_mpi_t *mpis = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t innerlen;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
if (!mpis)
{
err = gpg_error_from_syserror ();
goto out;
}
for (i = 0; i < elems_n; i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
if (!sig_value)
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
gcry_sexp_release (sublist);
sublist = NULL;
mpis[i] = sig_value;
}
if (err)
goto out;
/* ECDSA specific */
innerlen = 0;
for (i = 0; i < DIM(data); i++)
{
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
if (err)
goto out;
innerlen += 4 + data_n[i];
}
err = stream_write_uint32 (stream, innerlen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_string (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
mpint_list_free (mpis);
return err;
}
/* Signature encoder function for EdDSA. */
static gpg_error_t
ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
estream_t stream, gcry_sexp_t s_signature)
{
gpg_error_t err = 0;
gcry_sexp_t valuelist = NULL;
gcry_sexp_t sublist = NULL;
const char *elems;
size_t elems_n;
int i;
unsigned char *data[2] = {NULL, NULL};
size_t data_n[2];
size_t totallen = 0;
valuelist = gcry_sexp_nth (s_signature, 1);
if (!valuelist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
elems = spec->elems_signature;
elems_n = strlen (elems);
if (elems_n != DIM(data))
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
for (i = 0; i < DIM(data); i++)
{
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
if (!sublist)
{
err = gpg_error (GPG_ERR_INV_SEXP);
break;
}
data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
if (!data[i])
{
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
break;
}
totallen += data_n[i];
gcry_sexp_release (sublist);
sublist = NULL;
}
if (err)
goto out;
err = stream_write_uint32 (stream, totallen);
if (err)
goto out;
for (i = 0; i < DIM(data); i++)
{
err = stream_write_data (stream, data[i], data_n[i]);
if (err)
goto out;
}
out:
for (i = 0; i < DIM(data); i++)
xfree (data[i]);
gcry_sexp_release (valuelist);
gcry_sexp_release (sublist);
return err;
}
/*
S-Expressions.
*/
/* This function constructs a new S-Expression for the key identified
by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
be stored at R_SEXP. Returns an error code. */
static gpg_error_t
sexp_key_construct (gcry_sexp_t *r_sexp,
ssh_key_type_spec_t key_spec, int secret,
const char *curve_name, gcry_mpi_t *mpis,
const char *comment)
{
gpg_error_t err;
gcry_sexp_t sexp_new = NULL;
void *formatbuf = NULL;
void **arg_list = NULL;
estream_t format = NULL;
char *algo_name = NULL;
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* It is much easier and more readable to use a separate code
path for EdDSA. */
if (!curve_name)
err = gpg_error (GPG_ERR_INV_CURVE);
else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
else if (secret
&& (!mpis[1]
|| !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
err = gpg_error (GPG_ERR_BAD_SECKEY);
else if (secret)
err = gcry_sexp_build (&sexp_new, NULL,
"(private-key(ecc(curve %s)"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
curve_name,
mpis[0], mpis[1],
comment? comment:"");
else
err = gcry_sexp_build (&sexp_new, NULL,
"(public-key(ecc(curve %s)"
"(flags eddsa)(q %m))"
"(comment%s))",
curve_name,
mpis[0],
comment? comment:"");
}
else
{
const char *key_identifier[] = { "public-key", "private-key" };
int arg_idx;
const char *elems;
size_t elems_n;
unsigned int i, j;
if (secret)
elems = key_spec.elems_sexp_order;
else
elems = key_spec.elems_key_public;
elems_n = strlen (elems);
format = es_fopenmem (0, "a+b");
if (!format)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Key identifier, algorithm identifier, mpis, comment, and a NULL
as a safeguard. */
arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
if (!arg_list)
{
err = gpg_error_from_syserror ();
goto out;
}
arg_idx = 0;
es_fputs ("(%s(%s", format);
arg_list[arg_idx++] = &key_identifier[secret];
algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
if (!algo_name)
{
err = gpg_error_from_syserror ();
goto out;
}
strlwr (algo_name);
arg_list[arg_idx++] = &algo_name;
if (curve_name)
{
es_fputs ("(curve%s)", format);
arg_list[arg_idx++] = &curve_name;
}
for (i = 0; i < elems_n; i++)
{
es_fprintf (format, "(%c%%m)", elems[i]);
if (secret)
{
for (j = 0; j < elems_n; j++)
if (key_spec.elems_key_secret[j] == elems[i])
break;
}
else
j = i;
arg_list[arg_idx++] = &mpis[j];
}
es_fputs (")(comment%s))", format);
arg_list[arg_idx++] = &comment;
arg_list[arg_idx] = NULL;
es_putc (0, format);
if (es_ferror (format))
{
err = gpg_error_from_syserror ();
goto out;
}
if (es_fclose_snatch (format, &formatbuf, NULL))
{
err = gpg_error_from_syserror ();
goto out;
}
format = NULL;
err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
}
if (!err)
*r_sexp = sexp_new;
out:
es_fclose (format);
xfree (arg_list);
xfree (formatbuf);
xfree (algo_name);
return err;
}
/* This function extracts the key from the s-expression SEXP according
to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If
WITH_SECRET is true, the secret key parts are also extracted if
possible. Returns 0 on success or an error code. Note that data
stored at R_BLOB must be freed using es_free! */
static gpg_error_t
ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
ssh_key_type_spec_t key_spec,
void **r_blob, size_t *r_blob_size)
{
gpg_error_t err = 0;
gcry_sexp_t value_list = NULL;
gcry_sexp_t value_pair = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t blob_size;
const char *elems, *p_elems;
const char *data;
size_t datalen;
*r_blob = NULL;
*r_blob_size = 0;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Get the type of the key expression. */
data = gcry_sexp_nth_data (sexp, 0, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((datalen == 10 && !strncmp (data, "public-key", 10))
|| (datalen == 21 && !strncmp (data, "protected-private-key", 21))
|| (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
elems = key_spec.elems_key_public;
else if (datalen == 11 && !strncmp (data, "private-key", 11))
elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
else
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Get key value list. */
value_list = gcry_sexp_cadr (sexp);
if (!value_list)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
/* Write the ssh algorithm identifier. */
if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* Map the curve name to the ssh name. */
const char *name, *sshname, *canon_name;
name = gcry_pk_get_curve (sexp, 0, NULL);
if (!name)
{
err = gpg_error (GPG_ERR_INV_CURVE);
goto out;
}
sshname = ssh_identifier_from_curve_name (name, &canon_name);
if (!sshname)
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto out;
}
err = stream_write_cstring (stream, sshname);
if (err)
goto out;
err = stream_write_cstring (stream, canon_name);
if (err)
goto out;
}
else
{
/* Note: This is also used for EdDSA. */
err = stream_write_cstring (stream, key_spec.ssh_identifier);
if (err)
goto out;
}
/* Write the parameters. */
for (p_elems = elems; *p_elems; p_elems++)
{
gcry_sexp_release (value_pair);
value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
if (!value_pair)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
{
data = gcry_sexp_nth_data (value_pair, 1, &datalen);
if (!data)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
if (*p_elems == 'q' && datalen)
{ /* Remove the prefix 0x40. */
data++;
datalen--;
}
err = stream_write_string (stream, data, datalen);
if (err)
goto out;
}
else
{
gcry_mpi_t mpi;
/* Note that we need to use STD format; i.e. prepend a 0x00
to indicate a positive number if the high bit is set. */
mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
if (!mpi)
{
err = gpg_error (GPG_ERR_INV_SEXP);
goto out;
}
err = stream_write_mpi (stream, mpi);
gcry_mpi_release (mpi);
if (err)
goto out;
}
}
if (es_fclose_snatch (stream, &blob, &blob_size))
{
err = gpg_error_from_syserror ();
goto out;
}
stream = NULL;
*r_blob = blob;
blob = NULL;
*r_blob_size = blob_size;
out:
gcry_sexp_release (value_list);
gcry_sexp_release (value_pair);
es_fclose (stream);
es_free (blob);
return err;
}
/*
Key I/O.
*/
/* Search for a key specification entry. If SSH_NAME is not NULL,
search for an entry whose "ssh_name" is equal to SSH_NAME;
otherwise, search for an entry whose algorithm is equal to ALGO.
Store found entry in SPEC on success, return error otherwise. */
static gpg_error_t
ssh_key_type_lookup (const char *ssh_name, int algo,
ssh_key_type_spec_t *spec)
{
gpg_error_t err;
unsigned int i;
for (i = 0; i < DIM (ssh_key_types); i++)
if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
|| algo == ssh_key_types[i].algo)
break;
if (i == DIM (ssh_key_types))
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
*spec = ssh_key_types[i];
err = 0;
}
return err;
}
/* Receive a key from STREAM, according to the key specification given
as KEY_SPEC. Depending on SECRET, receive a secret or a public
key. If READ_COMMENT is true, receive a comment string as well.
Constructs a new S-Expression from received data and stores it in
KEY_NEW. Returns zero on success or an error code. */
static gpg_error_t
ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
int read_comment, ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
char *key_type = NULL;
char *comment = NULL;
estream_t cert = NULL;
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
gcry_mpi_t *mpi_list = NULL;
const char *elems;
const char *curve_name = NULL;
err = stream_read_cstring (stream, &key_type);
if (err)
goto out;
err = ssh_key_type_lookup (key_type, 0, &spec);
if (err)
goto out;
if ((spec.flags & SPEC_FLAG_WITH_CERT))
{
/* This is an OpenSSH certificate+private key. The certificate
is an SSH string and which we store in an estream object. */
unsigned char *buffer;
u32 buflen;
char *cert_key_type;
err = stream_read_string (stream, 0, &buffer, &buflen);
if (err)
goto out;
cert = es_fopenmem_init (0, "rb", buffer, buflen);
xfree (buffer);
if (!cert)
{
err = gpg_error_from_syserror ();
goto out;
}
/* Check that the key type matches. */
err = stream_read_cstring (cert, &cert_key_type);
if (err)
goto out;
if (strcmp (cert_key_type, key_type) )
{
xfree (cert_key_type);
log_error ("key types in received ssh certificate do not match\n");
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto out;
}
xfree (cert_key_type);
/* Skip the nonce. */
err = stream_read_string (cert, 0, NULL, NULL);
if (err)
goto out;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
/* The format of an EdDSA key is:
* string key_type ("ssh-ed25519")
* string public_key
* string private_key
*
* Note that the private key is the concatenation of the private
* key with the public key. Thus there's are 64 bytes; however
* we only want the real 32 byte private key - Libgcrypt expects
* this.
*/
mpi_list = xtrycalloc (3, sizeof *mpi_list);
if (!mpi_list)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
if (err)
goto out;
if (secret)
{
u32 len = 0;
unsigned char *buffer;
/* Read string length. */
err = stream_read_uint32 (stream, &len);
if (err)
goto out;
if (len != 32 && len != 64)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto out;
}
buffer = xtrymalloc_secure (32);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_read_data (stream, buffer, 32);
if (err)
{
xfree (buffer);
goto out;
}
mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
buffer = NULL;
if (len == 64)
{
err = stream_read_skip (stream, 32);
if (err)
goto out;
}
}
}
else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
{
/* The format of an ECDSA key is:
* string key_type ("ecdsa-sha2-nistp256" |
* "ecdsa-sha2-nistp384" |
* "ecdsa-sha2-nistp521" )
* string ecdsa_curve_name
* string ecdsa_public_key
* mpint ecdsa_private
*
* Note that we use the mpint reader instead of the string
* reader for ecsa_public_key. For the certificate variante
* ecdsa_curve_name+ecdsa_public_key are replaced by the
* certificate.
*/
unsigned char *buffer;
err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
if (err)
goto out;
/* Get the canonical name. Should be the same as the read
* string but we use this mapping to validate that name. */
if (!ssh_identifier_from_curve_name (buffer, &curve_name))
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
xfree (buffer);
goto out;
}
xfree (buffer);
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
else
{
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
if (err)
goto out;
}
if (read_comment)
{
err = stream_read_cstring (stream, &comment);
if (err)
goto out;
}
if (secret)
elems = spec.elems_key_secret;
else
elems = spec.elems_key_public;
if (spec.key_modifier)
{
err = (*spec.key_modifier) (elems, mpi_list);
if (err)
goto out;
}
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
if (secret)
{
err = gcry_sexp_build (&key, NULL,
"(private-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m)(d %m))"
"(comment%s))",
mpi_list[0], mpi_list[1],
comment? comment:"");
}
else
{
err = gcry_sexp_build (&key, NULL,
"(public-key(ecc(curve \"Ed25519\")"
"(flags eddsa)(q %m))"
"(comment%s))",
mpi_list[0],
comment? comment:"");
}
}
else
{
err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
comment? comment:"");
if (err)
goto out;
}
if (key_spec)
*key_spec = spec;
*key_new = key;
out:
es_fclose (cert);
mpint_list_free (mpi_list);
xfree (key_type);
xfree (comment);
return err;
}
/* Write the public key from KEY to STREAM in SSH key format. If
OVERRIDE_COMMENT is not NULL, it will be used instead of the
comment stored in the key. */
static gpg_error_t
ssh_send_key_public (estream_t stream, gcry_sexp_t key,
const char *override_comment)
{
ssh_key_type_spec_t spec;
int algo;
char *comment = NULL;
void *blob = NULL;
size_t bloblen;
gpg_error_t err = 0;
algo = get_pk_algo_from_key (key);
if (algo == 0)
goto out;
err = ssh_key_type_lookup (NULL, algo, &spec);
if (err)
goto out;
err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
if (err)
goto out;
err = stream_write_string (stream, blob, bloblen);
if (err)
goto out;
if (override_comment)
err = stream_write_cstring (stream, override_comment);
else
{
err = ssh_key_extract_comment (key, &comment);
if (err)
err = stream_write_cstring (stream, "(none)");
else
err = stream_write_cstring (stream, comment);
}
if (err)
goto out;
out:
xfree (comment);
es_free (blob);
return err;
}
/* Read a public key out of BLOB/BLOB_SIZE according to the key
specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
Returns zero on success or an error code. */
static gpg_error_t
ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
gcry_sexp_t *key_public,
ssh_key_type_spec_t *key_spec)
{
gpg_error_t err;
estream_t blob_stream;
blob_stream = es_fopenmem (0, "r+b");
if (!blob_stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (blob_stream, blob, blob_size);
if (err)
goto out;
err = es_fseek (blob_stream, 0, SEEK_SET);
if (err)
goto out;
err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
out:
es_fclose (blob_stream);
return err;
}
/* This function calculates the key grip for the key contained in the
S-Expression KEY and writes it to BUFFER, which must be large
enough to hold it. Returns usual error code. */
static gpg_error_t
ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
{
if (!gcry_pk_get_keygrip (key, buffer))
{
gpg_error_t err = gcry_pk_testkey (key);
return err? err : gpg_error (GPG_ERR_INTERNAL);
}
return 0;
}
/* Check whether a key of KEYGRIP on smartcard is available and
whether it has a usable key. Store a copy of that key at R_PK and
return 0. If no key is available store NULL at R_PK and return an
error code. If CARDSN is not NULL, a string with the serial number
of the card will be a malloced and stored there. */
static gpg_error_t
card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo,
gcry_sexp_t *r_pk, char **cardsn)
{
gpg_error_t err;
unsigned char *pkbuf;
size_t pkbuflen;
gcry_sexp_t s_pk;
unsigned char grip[20];
*r_pk = NULL;
if (cardsn)
*cardsn = NULL;
/* Read the public key. */
err = agent_card_readkey (ctrl, keyinfo->keygrip, &pkbuf, NULL);
if (err)
{
if (opt.verbose)
log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
return err;
}
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen);
if (err)
{
log_error ("failed to build S-Exp from received card key: %s\n",
gpg_strerror (err));
xfree (pkbuf);
return err;
}
hex2bin (keyinfo->keygrip, grip, sizeof (grip));
if ( agent_key_available (grip) )
{
/* (Shadow)-key is not available in our key storage. */
err = agent_write_shadow_key (grip, keyinfo->serialno,
keyinfo->idstr, pkbuf, 0);
if (err)
{
xfree (pkbuf);
gcry_sexp_release (s_pk);
return err;
}
}
if (cardsn)
{
char *dispsn;
/* If the card handler is able to return a short serialnumber,
use that one, else use the complete serialno. */
if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn,
keyinfo->keygrip))
{
*cardsn = xtryasprintf ("cardno:%s", dispsn);
xfree (dispsn);
}
else
*cardsn = xtryasprintf ("cardno:%s", keyinfo->serialno);
if (!*cardsn)
{
err = gpg_error_from_syserror ();
xfree (pkbuf);
gcry_sexp_release (s_pk);
return err;
}
}
xfree (pkbuf);
*r_pk = s_pk;
return 0;
}
/*
Request handler. Each handler is provided with a CTRL context, a
REQUEST object and a RESPONSE object. The actual request is to be
read from REQUEST, the response needs to be written to RESPONSE.
*/
/* Handler for the "request_identities" command. */
static gpg_error_t
ssh_handler_request_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
u32 key_counter;
estream_t key_blobs;
gcry_sexp_t key_public;
gpg_error_t err;
int ret;
ssh_control_file_t cf = NULL;
gpg_error_t ret_err;
(void)request;
/* Prepare buffer stream. */
key_public = NULL;
key_counter = 0;
key_blobs = es_fopenmem (0, "r+b");
if (! key_blobs)
{
err = gpg_error_from_syserror ();
goto out;
}
/* First check whether a key is currently available in the card
reader - this should be allowed even without being listed in
sshcontrol. */
if (!opt.disable_scdaemon)
{
char *serialno;
struct card_key_info_s *keyinfo_list;
struct card_key_info_s *keyinfo;
/* Scan device(s), and get list of KEYGRIP. */
err = agent_card_serialno (ctrl, &serialno, NULL);
if (!err)
{
xfree (serialno);
err = agent_card_keyinfo (ctrl, NULL, GCRY_PK_USAGE_AUTH,
&keyinfo_list);
}
if (err)
{
if (opt.verbose)
log_info (_("error getting list of cards: %s\n"),
gpg_strerror (err));
goto scd_out;
}
for (keyinfo = keyinfo_list; keyinfo; keyinfo = keyinfo->next)
{
char *cardsn;
if (card_key_available (ctrl, keyinfo, &key_public, &cardsn))
continue;
err = ssh_send_key_public (key_blobs, key_public, cardsn);
if (err && opt.verbose)
gcry_log_debugsxp ("pubkey", key_public);
gcry_sexp_release (key_public);
key_public = NULL;
xfree (cardsn);
if (err)
{
agent_card_free_keyinfo (keyinfo_list);
goto out;
}
key_counter++;
}
agent_card_free_keyinfo (keyinfo_list);
}
scd_out:
/* Then look at all the registered and non-disabled keys. */
err = open_control_file (&cf, 0);
if (err)
goto out;
while (!read_control_file_item (cf))
{
unsigned char grip[20];
if (!cf->item.valid)
continue; /* Should not happen. */
if (cf->item.disabled)
continue;
log_assert (strlen (cf->item.hexgrip) == 40);
hex2bin (cf->item.hexgrip, grip, sizeof (grip));
err = agent_public_key_from_file (ctrl, grip, &key_public);
if (err)
{
log_error ("%s:%d: key '%s' skipped: %s\n",
cf->fname, cf->lnr, cf->item.hexgrip,
gpg_strerror (err));
continue;
}
err = ssh_send_key_public (key_blobs, key_public, NULL);
if (err)
goto out;
gcry_sexp_release (key_public);
key_public = NULL;
key_counter++;
}
err = 0;
ret = es_fseek (key_blobs, 0, SEEK_SET);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
out:
/* Send response. */
gcry_sexp_release (key_public);
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
if (!ret_err)
ret_err = stream_write_uint32 (response, key_counter);
if (!ret_err)
ret_err = stream_copy (response, key_blobs);
}
else
{
log_error ("ssh request identities failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
}
es_fclose (key_blobs);
close_control_file (cf);
return ret_err;
}
/* This function hashes the data contained in DATA of size DATA_N
according to the message digest algorithm specified by MD_ALGORITHM
and writes the message digest to HASH, which needs to large enough
for the digest. */
static gpg_error_t
data_hash (unsigned char *data, size_t data_n,
int md_algorithm, unsigned char *hash)
{
gcry_md_hash_buffer (md_algorithm, hash, data, data_n);
return 0;
}
/* This function signs the data described by CTRL. If HASH is not
NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to
allow the use of signature algorithms that implement the hashing
internally (e.g. Ed25519). On success the created signature is
stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
must use es_free to release this memory. */
static gpg_error_t
data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
const void *hash, size_t hashlen,
unsigned char **r_sig, size_t *r_siglen)
{
gpg_error_t err;
gcry_sexp_t signature_sexp = NULL;
estream_t stream = NULL;
void *blob = NULL;
size_t bloblen;
char hexgrip[40+1];
*r_sig = NULL;
*r_siglen = 0;
/* Quick check to see whether we have a valid keygrip and convert it
to hex. */
if (!ctrl->have_keygrip)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto out;
}
bin2hex (ctrl->keygrip, 20, hexgrip);
/* Ask for confirmation if needed. */
if (confirm_flag_from_sshcontrol (hexgrip))
{
gcry_sexp_t key;
char *fpr, *prompt;
char *comment = NULL;
err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
if (err)
goto out;
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
if (!err)
{
gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
if (tmpsxp)
comment = gcry_sexp_nth_string (tmpsxp, 1);
gcry_sexp_release (tmpsxp);
}
gcry_sexp_release (key);
if (err)
goto out;
prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
" %s%%0A"
" (%s)%%0A"
"Do you want to allow this?"),
fpr, comment? comment:"");
xfree (fpr);
gcry_free (comment);
err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
xfree (prompt);
if (err)
goto out;
}
/* Create signature. */
ctrl->use_auth_call = 1;
err = agent_pksign_do (ctrl, NULL,
L_("Please enter the passphrase "
"for the ssh key%%0A %F%%0A (%c)"),
&signature_sexp,
CACHE_MODE_SSH, ttl_from_sshcontrol,
hash, hashlen);
ctrl->use_auth_call = 0;
if (err)
goto out;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_cstring (stream, spec->ssh_identifier);
if (err)
goto out;
err = spec->signature_encoder (spec, stream, signature_sexp);
if (err)
goto out;
err = es_fclose_snatch (stream, &blob, &bloblen);
if (err)
goto out;
stream = NULL;
*r_sig = blob; blob = NULL;
*r_siglen = bloblen;
out:
xfree (blob);
es_fclose (stream);
gcry_sexp_release (signature_sexp);
return err;
}
/* Handler for the "sign_request" command. */
static gpg_error_t
ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
{
gcry_sexp_t key = NULL;
ssh_key_type_spec_t spec;
unsigned char hash[MAX_DIGEST_LEN];
unsigned int hash_n;
unsigned char key_grip[20];
unsigned char *key_blob = NULL;
u32 key_blob_size;
unsigned char *data = NULL;
unsigned char *sig = NULL;
size_t sig_n;
u32 data_size;
gpg_error_t err;
gpg_error_t ret_err;
int hash_algo;
/* Receive key. */
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec);
if (err)
goto out;
/* Receive data to sign. */
err = stream_read_string (request, 0, &data, &data_size);
if (err)
goto out;
/* Flag processing. */
{
u32 flags;
err = stream_read_uint32 (request, &flags);
if (err)
goto out;
if (spec.algo == GCRY_PK_RSA)
{
if ((flags & SSH_AGENT_RSA_SHA2_512))
{
flags &= ~SSH_AGENT_RSA_SHA2_512;
spec.ssh_identifier = "rsa-sha2-512";
spec.hash_algo = GCRY_MD_SHA512;
}
if ((flags & SSH_AGENT_RSA_SHA2_256))
{
/* Note: We prefer SHA256 over SHA512. */
flags &= ~SSH_AGENT_RSA_SHA2_256;
spec.ssh_identifier = "rsa-sha2-256";
spec.hash_algo = GCRY_MD_SHA256;
}
}
/* Some flag is present that we do not know about. Note that
* processed or known flags have been cleared at this point. */
if (flags)
{
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
goto out;
}
}
hash_algo = spec.hash_algo;
if (!hash_algo)
hash_algo = GCRY_MD_SHA1; /* Use the default. */
ctrl->digest.algo = hash_algo;
if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
ctrl->digest.raw_value = 0;
else
ctrl->digest.raw_value = 1;
/* Calculate key grip. */
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
ctrl->have_keygrip = 1;
memcpy (ctrl->keygrip, key_grip, 20);
/* Hash data unless we use EdDSA. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
{
ctrl->digest.valuelen = 0;
}
else
{
hash_n = gcry_md_get_algo_dlen (hash_algo);
if (!hash_n)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
err = data_hash (data, data_size, hash_algo, hash);
if (err)
goto out;
memcpy (ctrl->digest.value, hash, hash_n);
ctrl->digest.valuelen = hash_n;
}
/* Sign data. */
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
else
err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
out:
/* Done. */
if (!err)
{
ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
if (ret_err)
goto leave;
ret_err = stream_write_string (response, sig, sig_n);
if (ret_err)
goto leave;
}
else
{
log_error ("ssh sign request failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
if (ret_err)
goto leave;
}
leave:
gcry_sexp_release (key);
xfree (key_blob);
xfree (data);
es_free (sig);
return ret_err;
}
/* This function extracts the comment contained in the key
s-expression KEY and stores a copy in COMMENT. Returns usual error
code. */
static gpg_error_t
ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
{
gcry_sexp_t comment_list;
*r_comment = NULL;
comment_list = gcry_sexp_find_token (key, "comment", 0);
if (!comment_list)
return gpg_error (GPG_ERR_INV_SEXP);
*r_comment = gcry_sexp_nth_string (comment_list, 1);
gcry_sexp_release (comment_list);
if (!*r_comment)
return gpg_error (GPG_ERR_INV_SEXP);
return 0;
}
/* This function converts the key contained in the S-Expression KEY
into a buffer, which is protected by the passphrase PASSPHRASE.
If PASSPHRASE is the empty passphrase, the key is not protected.
Returns usual error code. */
static gpg_error_t
ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
unsigned char **buffer, size_t *buffer_n)
{
unsigned char *buffer_new;
unsigned int buffer_new_n;
gpg_error_t err;
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
buffer_new = xtrymalloc_secure (buffer_new_n);
if (! buffer_new)
{
err = gpg_error_from_syserror ();
goto out;
}
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON,
buffer_new, buffer_new_n);
if (*passphrase)
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
else
{
/* The key derivation function does not support zero length
* strings. Store key unprotected if the user wishes so. */
*buffer = buffer_new;
*buffer_n = buffer_new_n;
buffer_new = NULL;
err = 0;
}
out:
xfree (buffer_new);
return err;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_compare_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Store the ssh KEY into our local key storage and protect it after
asking for a passphrase. Cache that passphrase. TTL is the
maximum caching time for that key. If the key already exists in
our key storage, don't do anything. When entering a key also add
an entry to the sshcontrol file. */
static gpg_error_t
ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
gcry_sexp_t key, int ttl, int confirm)
{
gpg_error_t err;
unsigned char key_grip_raw[20];
char key_grip[41];
unsigned char *buffer = NULL;
size_t buffer_n;
char *description = NULL;
const char *description2 = L_("Please re-enter this passphrase");
char *comment = NULL;
char *key_fpr = NULL;
const char *initial_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
err = ssh_key_grip (key, key_grip_raw);
if (err)
goto out;
bin2hex (key_grip_raw, 20, key_grip);
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
if (err)
goto out;
/* Check whether the key is already in our key storage. Don't do
anything then besides (re-)adding it to sshcontrol. */
if ( !agent_key_available (key_grip_raw) )
goto key_exists; /* Yes, key is available. */
err = ssh_key_extract_comment (key, &comment);
if (err)
goto out;
if ( asprintf (&description,
L_("Please enter a passphrase to protect"
" the received secret key%%0A"
" %s%%0A"
" %s%%0A"
"within gpg-agent's key storage"),
key_fpr, comment ? comment : "") < 0)
{
err = gpg_error_from_syserror ();
goto out;
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
err = gpg_error_from_syserror ();
goto out;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
err = gpg_error_from_syserror ();
goto out;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 1;
pi->with_repeat = 1;
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 1;
pi2->check_cb = reenter_compare_cb;
pi2->check_cb_arg = pi->pin;
next_try:
err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
initial_errtext = NULL;
if (err)
goto out;
/* Unless the passphrase is empty or the pinentry told us that
it already did the repetition check, ask to confirm it. */
if (*pi->pin && !pi->repeat_okay)
{
err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered one did not match and the user did not
hit cancel. */
initial_errtext = L_("does not match - try again");
goto next_try;
}
}
err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n);
if (err)
goto out;
/* Store this key to our key storage. */
err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, NULL, NULL);
if (err)
goto out;
/* Cache this passphrase. */
err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl);
if (err)
goto out;
key_exists:
/* And add an entry to the sshcontrol file. */
err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
out:
if (pi2 && pi2->max_length)
wipememory (pi2->pin, pi2->max_length);
xfree (pi2);
if (pi && pi->max_length)
wipememory (pi->pin, pi->max_length);
xfree (pi);
xfree (buffer);
xfree (comment);
xfree (key_fpr);
xfree (description);
return err;
}
/* This function removes the key contained in the S-Expression KEY
from the local key storage, in case it exists there. Returns usual
error code. FIXME: this function is a stub. */
static gpg_error_t
ssh_identity_drop (gcry_sexp_t key)
{
unsigned char key_grip[21] = { 0 };
gpg_error_t err;
err = ssh_key_grip (key, key_grip);
if (err)
goto out;
key_grip[sizeof (key_grip) - 1] = 0;
/* FIXME: What to do here - forgetting the passphrase or deleting
the key from key cache? */
out:
return err;
}
/* Handler for the "add_identity" command. */
static gpg_error_t
ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
ssh_key_type_spec_t spec;
gpg_error_t err;
gcry_sexp_t key;
unsigned char b;
int confirm;
int ttl;
confirm = 0;
key = NULL;
ttl = 0;
/* FIXME? */
err = ssh_receive_key (request, &key, 1, 1, &spec);
if (err)
goto out;
while (1)
{
err = stream_read_byte (request, &b);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
break;
}
switch (b)
{
case SSH_OPT_CONSTRAIN_LIFETIME:
{
u32 n = 0;
err = stream_read_uint32 (request, &n);
if (! err)
ttl = n;
break;
}
case SSH_OPT_CONSTRAIN_CONFIRM:
{
confirm = 1;
break;
}
default:
/* FIXME: log/bad? */
break;
}
}
if (err)
goto out;
err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
out:
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "remove_identity" command. */
static gpg_error_t
ssh_handler_remove_identity (ctrl_t ctrl,
estream_t request, estream_t response)
{
unsigned char *key_blob;
u32 key_blob_size;
gcry_sexp_t key;
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
/* Receive key. */
key_blob = NULL;
key = NULL;
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
if (err)
goto out;
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL);
if (err)
goto out;
err = ssh_identity_drop (key);
out:
xfree (key_blob);
gcry_sexp_release (key);
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* FIXME: stub function. Actually useful? */
static gpg_error_t
ssh_identities_remove_all (void)
{
gpg_error_t err;
err = 0;
/* FIXME: shall we remove _all_ cache entries or only those
registered through the ssh-agent protocol? */
return err;
}
/* Handler for the "remove_all_identities" command. */
static gpg_error_t
ssh_handler_remove_all_identities (ctrl_t ctrl,
estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_identities_remove_all ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Lock agent? FIXME: stub function. */
static gpg_error_t
ssh_lock (void)
{
gpg_error_t err;
/* FIXME */
log_error ("ssh-agent's lock command is not implemented\n");
err = 0;
return err;
}
/* Unock agent? FIXME: stub function. */
static gpg_error_t
ssh_unlock (void)
{
gpg_error_t err;
log_error ("ssh-agent's unlock command is not implemented\n");
err = 0;
return err;
}
/* Handler for the "lock" command. */
static gpg_error_t
ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_lock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Handler for the "unlock" command. */
static gpg_error_t
ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
{
gpg_error_t ret_err;
gpg_error_t err;
(void)ctrl;
(void)request;
err = ssh_unlock ();
if (! err)
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
else
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
return ret_err;
}
/* Return the request specification for the request identified by TYPE
or NULL in case the requested request specification could not be
found. */
static const ssh_request_spec_t *
request_spec_lookup (int type)
{
const ssh_request_spec_t *spec;
unsigned int i;
for (i = 0; i < DIM (request_specs); i++)
if (request_specs[i].type == type)
break;
if (i == DIM (request_specs))
{
if (opt.verbose)
log_info ("ssh request %u is not supported\n", type);
spec = NULL;
}
else
spec = request_specs + i;
return spec;
}
/* Process a single request. The request is read from and the
response is written to STREAM_SOCK. Uses CTRL as context. Returns
zero in case of success, non zero in case of failure. */
static int
ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
{
const ssh_request_spec_t *spec;
estream_t response = NULL;
estream_t request = NULL;
unsigned char request_type;
gpg_error_t err;
int send_err = 0;
int ret;
unsigned char *request_data = NULL;
u32 request_data_size;
u32 response_size;
/* Create memory streams for request/response data. The entire
request will be stored in secure memory, since it might contain
secret key material. The response does not have to be stored in
secure memory, since we never give out secret keys.
Note: we only have little secure memory, but there is NO
possibility of DoS here; only trusted clients are allowed to
connect to the agent. What could happen is that the agent
returns out-of-secure-memory errors on requests in case the
agent's owner floods his own agent with many large messages.
-moritz */
/* Retrieve request. */
err = stream_read_string (stream_sock, 1, &request_data, &request_data_size);
if (err)
goto out;
if (opt.verbose > 1)
log_info ("received ssh request of length %u\n",
(unsigned int)request_data_size);
if (! request_data_size)
{
send_err = 1;
goto out;
/* Broken request; FIXME. */
}
request_type = request_data[0];
spec = request_spec_lookup (request_type);
if (! spec)
{
send_err = 1;
goto out;
/* Unknown request; FIXME. */
}
if (spec->secret_input)
request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
else
request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
if (! request)
{
err = gpg_error_from_syserror ();
goto out;
}
ret = es_setvbuf (request, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
goto out;
}
err = stream_write_data (request, request_data + 1, request_data_size - 1);
if (err)
goto out;
es_rewind (request);
response = es_fopenmem (0, "r+b");
if (! response)
{
err = gpg_error_from_syserror ();
goto out;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request, response);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
if (err)
{
send_err = 1;
goto out;
}
response_size = es_ftell (response);
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
err = es_fseek (response, 0, SEEK_SET);
if (err)
{
send_err = 1;
goto out;
}
err = stream_write_uint32 (stream_sock, response_size);
if (err)
{
send_err = 1;
goto out;
}
err = stream_copy (stream_sock, response);
if (err)
goto out;
err = es_fflush (stream_sock);
if (err)
goto out;
out:
if (err && es_feof (stream_sock))
log_error ("error occurred while processing request: %s\n",
gpg_strerror (err));
if (send_err)
{
if (opt.verbose > 1)
log_info ("sending ssh error response\n");
err = stream_write_uint32 (stream_sock, 1);
if (err)
goto leave;
err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE);
if (err)
goto leave;
}
leave:
es_fclose (request);
es_fclose (response);
xfree (request_data);
return !!err;
}
/* Return the peer's pid. */
static void
get_client_info (int fd, struct peer_info_s *out)
{
pid_t client_pid = (pid_t)(-1);
int client_uid = -1;
#ifdef SO_PEERCRED
{
#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
struct sockpeercred cr;
#else
struct ucred cr;
#endif
socklen_t cl = sizeof cr;
if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
{
#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
client_pid = cr.pid;
client_uid = (int)cr.uid;
#elif defined (HAVE_STRUCT_UCRED_CR_PID)
client_pid = cr.cr_pid;
client_uid = (int)cr.cr_uid;
#else
#error "Unknown SO_PEERCRED struct"
#endif
}
}
#elif defined (LOCAL_PEERPID)
{
socklen_t len = sizeof (pid_t);
getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
#if defined (LOCAL_PEERCRED)
{
struct xucred cr;
len = sizeof (struct xucred);
if (!getsockopt (fd, SOL_LOCAL, LOCAL_PEERCRED, &cr, &len))
client_uid = (int)cr.cr_uid;
}
#endif
}
#elif defined (LOCAL_PEEREID)
{
struct unpcbid unp;
socklen_t unpl = sizeof unp;
if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
{
client_pid = unp.unp_pid;
client_uid = (int)unp.unp_euid;
}
}
#elif defined (HAVE_GETPEERUCRED)
{
ucred_t *ucred = NULL;
if (getpeerucred (fd, &ucred) != -1)
{
client_pid = ucred_getpid (ucred);
client_uid = (int)ucred_geteuid (ucred);
ucred_free (ucred);
}
}
#else
(void)fd;
#endif
out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid);
out->uid = client_uid;
}
/* Start serving client on SOCK_CLIENT. */
void
start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
{
estream_t stream_sock = NULL;
gpg_error_t err;
int ret;
struct peer_info_s peer_info;
err = agent_copy_startup_env (ctrl);
if (err)
goto out;
get_client_info (FD2INT(sock_client), &peer_info);
ctrl->client_pid = peer_info.pid;
ctrl->client_uid = peer_info.uid;
/* Create stream from socket. */
stream_sock = es_fdopen (FD2INT(sock_client), "r+");
if (!stream_sock)
{
err = gpg_error_from_syserror ();
log_error (_("failed to create stream from socket: %s\n"),
gpg_strerror (err));
goto out;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
ret = es_setvbuf (stream_sock, NULL, _IONBF, 0);
if (ret)
{
err = gpg_error_from_syserror ();
log_error ("failed to disable buffering "
"on socket stream: %s\n", gpg_strerror (err));
goto out;
}
/* Main processing loop. */
while ( !ssh_request_process (ctrl, stream_sock) )
{
/* Check whether we have reached EOF before trying to read
another request. */
int c;
c = es_fgetc (stream_sock);
if (c == EOF)
break;
es_ungetc (c, stream_sock);
}
/* Reset the SCD in case it has been used. */
agent_reset_scd (ctrl);
out:
if (stream_sock)
es_fclose (stream_sock);
}
#ifdef HAVE_W32_SYSTEM
/* Serve one ssh-agent request. This is used for the Putty support.
REQUEST is the mmapped memory which may be accessed up to a
length of MAXREQLEN. Returns 0 on success which also indicates
that a valid SSH response message is now in REQUEST. */
int
serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen)
{
gpg_error_t err;
int send_err = 0;
int valid_response = 0;
const ssh_request_spec_t *spec;
u32 msglen;
estream_t request_stream, response_stream;
if (agent_copy_startup_env (ctrl))
goto leave; /* Error setting up the environment. */
if (maxreqlen < 5)
goto leave; /* Caller error. */
msglen = uint32_construct (request[0], request[1], request[2], request[3]);
if (msglen < 1 || msglen > maxreqlen - 4)
{
log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
goto leave;
}
spec = request_spec_lookup (request[4]);
if (!spec)
{
send_err = 1; /* Unknown request type. */
goto leave;
}
/* Create a stream object with the data part of the request. */
if (spec->secret_input)
request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
else
request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
if (!request_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We have to disable the estream buffering, because the estream
core doesn't know about secure memory. */
if (es_setvbuf (request_stream, NULL, _IONBF, 0))
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy the request to the stream but omit the request type. */
err = stream_write_data (request_stream, request + 5, msglen - 1);
if (err)
goto leave;
es_rewind (request_stream);
response_stream = es_fopenmem (0, "r+b");
if (!response_stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
log_info ("ssh request handler for %s (%u) started\n",
spec->identifier, spec->type);
err = (*spec->handler) (ctrl, request_stream, response_stream);
if (opt.verbose)
{
if (err)
log_info ("ssh request handler for %s (%u) failed: %s\n",
spec->identifier, spec->type, gpg_strerror (err));
else
log_info ("ssh request handler for %s (%u) ready\n",
spec->identifier, spec->type);
}
es_fclose (request_stream);
request_stream = NULL;
if (err)
{
send_err = 1;
goto leave;
}
/* Put the response back into the mmapped buffer. */
{
void *response_data;
size_t response_size;
/* NB: In contrast to the request-stream, the response stream
includes the message type byte. */
if (es_fclose_snatch (response_stream, &response_data, &response_size))
{
log_error ("snatching ssh response failed: %s",
gpg_strerror (gpg_error_from_syserror ()));
send_err = 1; /* Ooops. */
goto leave;
}
if (opt.verbose > 1)
log_info ("sending ssh response of length %u\n",
(unsigned int)response_size);
if (response_size > maxreqlen - 4)
{
log_error ("invalid length of the ssh response: %s",
gpg_strerror (GPG_ERR_INTERNAL));
es_free (response_data);
send_err = 1;
goto leave;
}
request[0] = response_size >> 24;
request[1] = response_size >> 16;
request[2] = response_size >> 8;
request[3] = response_size >> 0;
memcpy (request+4, response_data, response_size);
es_free (response_data);
valid_response = 1;
}
leave:
if (send_err)
{
request[0] = 0;
request[1] = 0;
request[2] = 0;
request[3] = 1;
request[4] = SSH_RESPONSE_FAILURE;
valid_response = 1;
}
/* Reset the SCD in case it has been used. */
agent_reset_scd (ctrl);
return valid_response? 0 : -1;
}
#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/command.c b/agent/command.c
index 1b07600c3..6f179cb08 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1,3748 +1,3748 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001-2011 Free Software Foundation, Inc.
* Copyright (C) 2001-2013 Werner Koch
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* FIXME: we should not use the default assuan buffering but setup
some buffering in secure mempory to protect session keys etc. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "agent.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/ssh-utils.h"
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum allowed size of the inquired ciphertext. */
#define MAXLEN_CIPHERTEXT 4096
/* Maximum allowed size of the key parameters. */
#define MAXLEN_KEYPARAM 1024
/* Maximum allowed size of key data as used in inquiries (bytes). */
#define MAXLEN_KEYDATA 8192
/* Maximum length of a secret to store under one key. */
#define MAXLEN_PUT_SECRET 4096
/* The size of the import/export KEK key (in bytes). */
#define KEYWRAP_KEYSIZE (128/8)
/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
text string. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Check that the maximum digest length we support has at least the
length of the keygrip. */
#if MAX_DIGEST_LEN < 20
#error MAX_DIGEST_LEN shorter than keygrip
#endif
/* Data used to associate an Assuan context with local server data.
This is this modules local part of the server_control_s struct. */
struct server_local_s
{
/* Our Assuan context. */
assuan_context_t assuan_ctx;
/* If this flag is true, the passphrase cache is used for signing
operations. It defaults to true but may be set on a per
connection base. The global option opt.ignore_cache_for_signing
takes precedence over this flag. */
unsigned int use_cache_for_signing : 1;
/* Flag to suppress I/O logging during a command. */
unsigned int pause_io_logging : 1;
/* Flag indicating that the connection is from ourselves. */
unsigned int connect_from_self : 1;
/* Helper flag for io_monitor to allow suppressing of our own
* greeting in some cases. See io_monitor for details. */
unsigned int greeting_seen : 1;
/* If this flag is set to true the agent will be terminated after
the end of the current session. */
unsigned int stopme : 1;
/* Flag indicating whether pinentry notifications shall be done. */
unsigned int allow_pinentry_notify : 1;
/* An allocated description for the next key operation. This is
used if a pinnetry needs to be popped up. */
char *keydesc;
/* Malloced KEK (Key-Encryption-Key) for the import_key command. */
void *import_key;
/* Malloced KEK for the export_key command. */
void *export_key;
/* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
int allow_fully_canceled;
/* Last CACHE_NONCE sent as status (malloced). */
char *last_cache_nonce;
/* Last PASSWD_NONCE sent as status (malloced). */
char *last_passwd_nonce;
/* Per connection cache of the keyinfo from the cards. The
* eventcounters for cards at the time the info was fetched is
* stored here as a freshness indicator. */
struct {
struct card_key_info_s *ki;
unsigned int eventno;
unsigned int maybe_key_change;
} last_card_keyinfo;
};
/* An entry for the getval/putval commands. */
struct putval_item_s
{
struct putval_item_s *next;
size_t off; /* Offset to the value into DATA. */
size_t len; /* Length of the value. */
char d[1]; /* Key | Nul | value. */
};
/* A list of key value pairs fpr the getval/putval commands. */
static struct putval_item_s *putval_list;
/* To help polling clients, we keep track of the number of certain
events. This structure keeps those counters. The counters are
integers and there should be no problem if they are overflowing as
callers need to check only whether a counter changed. The actual
values are not meaningful. */
struct
{
/* Incremented if any of the other counters below changed. */
unsigned int any;
/* Incremented if a key is added or removed from the internal privat
key database. */
unsigned int key;
/* Incremented if a change of the card readers stati has been
detected. */
unsigned int card;
/* Internal counter to track possible changes to a key.
* FIXME: This should be replaced by generic notifications from scd.
*/
unsigned int maybe_key_change;
} eventcounter;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Release the memory buffer MB but first wipe out the used memory. */
static void
clear_outbuf (membuf_t *mb)
{
void *p;
size_t n;
p = get_membuf (mb, &n);
if (p)
{
wipememory (p, n);
xfree (p);
}
}
/* Write the content of memory buffer MB as assuan data to CTX and
wipe the buffer out afterwards. */
static gpg_error_t
write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
{
gpg_error_t ae;
void *p;
size_t n;
p = get_membuf (mb, &n);
if (!p)
return out_of_core ();
ae = assuan_send_data (ctx, p, n);
memset (p, 0, n);
xfree (p);
return ae;
}
/* Clear the nonces used to enable the passphrase cache for certain
multi-command command sequences. */
static void
clear_nonce_cache (ctrl_t ctrl)
{
if (ctrl->server_local->last_cache_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = NULL;
}
if (ctrl->server_local->last_passwd_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = NULL;
}
}
/* This function is called by Libassuan whenever the client sends a
reset. It has been registered similar to the other Assuan
commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
memset (ctrl->keygrip, 0, 20);
ctrl->have_keygrip = 0;
ctrl->digest.valuelen = 0;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
clear_nonce_cache (ctrl);
return 0;
}
/* Replace all '+' by a blank in the string S. */
static void
plus_to_blank (char *s)
{
for (; *s; s++)
{
if (*s == '+')
*s = ' ';
}
}
/* Parse a hex string. Return an Assuan error code or 0 on success and the
length of the parsed string in LEN. */
static int
parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
{
const char *p;
size_t n;
/* parse the hash value */
for (p=string, n=0; hexdigitp (p); p++, n++)
;
if (*p != ' ' && *p != '\t' && *p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
*len = n;
return 0;
}
/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
provide space for 20 bytes. BUF is not changed if the function
returns an error. */
static int
parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
{
int rc;
size_t n = 0;
rc = parse_hexstring (ctx, string, &n);
if (rc)
return rc;
n /= 2;
if (n != 20)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
if (hex2bin (string, buf, 20) < 0)
return set_error (GPG_ERR_BUG, "hex2bin");
return 0;
}
/* Parse the TTL from STRING. Leading and trailing spaces are
* skipped. The value is constrained to -1 .. MAXINT. On error 0 is
* returned, else the number of bytes scanned. */
static size_t
parse_ttl (const char *string, int *r_ttl)
{
const char *string_orig = string;
long ttl;
char *pend;
ttl = strtol (string, &pend, 10);
string = pend;
if (string == string_orig || !(spacep (string) || !*string)
|| ttl < -1L || (int)ttl != (long)ttl)
{
*r_ttl = 0;
return 0;
}
while (spacep (string) || *string== '\n')
string++;
*r_ttl = (int)ttl;
return string - string_orig;
}
/* Write an Assuan status line. KEYWORD is the first item on the
* status line. The following arguments are all separated by a space
* in the output. The last argument must be a NULL. Linefeeds and
* carriage returns characters (which are not allowed in an Assuan
* status line) are silently quoted in C-style. */
gpg_error_t
agent_write_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
arg instead of an assuan context as first argument. */
gpg_error_t
agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about a launched Pinentry. Because
that might disturb some older clients, this is only done if enabled
via an option. Returns an gpg error code. */
gpg_error_t
agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
{
char line[256];
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
pid, extra?" ":"", extra? extra:"");
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/* An agent progress callback for Libgcrypt. This has been registered
* to be called via the progress dispatcher mechanism from
* gpg-agent.c */
static void
progress_cb (ctrl_t ctrl, const char *what, int printchar,
int current, int total)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
;
else if (printchar == '\n' && what && !strcmp (what, "primegen"))
agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
else
agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total);
}
/* Helper to print a message while leaving a command. Note that this
* function does not call assuan_set_error; the caller may do this
* prior to calling us. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
/* Not all users of gpg-agent know about the fully canceled
error code; map it back if needed. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->server_local->allow_fully_canceled)
err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
}
/* Most code from common/ does not know the error source, thus
we fix this here. */
if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
static const char hlp_geteventcounter[] =
"GETEVENTCOUNTER\n"
"\n"
"Return a status line named EVENTCOUNTER with the current values\n"
"of all event counters. The values are decimal numbers in the range\n"
"0 to UINT_MAX and wrapping around to 0. The actual values should\n"
"not be relied upon, they shall only be used to detect a change.\n"
"\n"
"The currently defined counters are:\n"
"\n"
"ANY - Incremented with any change of any of the other counters.\n"
"KEY - Incremented for added or removed private keys.\n"
"CARD - Incremented for changes of the card readers stati.";
static gpg_error_t
cmd_geteventcounter (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
eventcounter.any,
eventcounter.key,
eventcounter.card);
}
/* This function should be called once for all key removals or
additions. This function is assured not to do any context
switches. */
void
bump_key_eventcounter (void)
{
eventcounter.key++;
eventcounter.any++;
}
/* This function should be called for all card reader status
changes. This function is assured not to do any context
switches. */
void
bump_card_eventcounter (void)
{
eventcounter.card++;
eventcounter.any++;
}
static const char hlp_istrusted[] =
"ISTRUSTED <hexstring_with_fingerprint>\n"
"\n"
"Return OK when we have an entry with this fingerprint in our\n"
"trustlist";
static gpg_error_t
cmd_istrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
/* Parse the fingerprint value. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
rc = agent_istrusted (ctrl, fpr, NULL);
if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
return rc;
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
return gpg_error (GPG_ERR_NOT_TRUSTED);
else
return leave_cmd (ctx, rc);
}
static const char hlp_listtrusted[] =
"LISTTRUSTED\n"
"\n"
"List all entries from the trustlist.";
static gpg_error_t
cmd_listtrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
rc = agent_listtrusted (ctx);
return leave_cmd (ctx, rc);
}
static const char hlp_martrusted[] =
"MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
"\n"
"Store a new key in into the trustlist.";
static gpg_error_t
cmd_marktrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
int flag;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the fingerprint value */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (!spacep (p) || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
while (spacep (p))
p++;
flag = *p++;
if ( (flag != 'S' && flag != 'P') || !spacep (p) )
return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
while (spacep (p))
p++;
rc = agent_marktrusted (ctrl, p, fpr, flag);
return leave_cmd (ctx, rc);
}
static const char hlp_havekey[] =
"HAVEKEY <hexstrings_with_keygrips>\n"
"\n"
"Return success if at least one of the secret keys with the given\n"
"keygrips is available.";
static gpg_error_t
cmd_havekey (assuan_context_t ctx, char *line)
{
gpg_error_t err;
unsigned char buf[20];
do
{
err = parse_keygrip (ctx, line, buf);
if (err)
return err;
if (!agent_key_available (buf))
return 0; /* Found. */
while (*line && *line != ' ' && *line != '\t')
line++;
while (*line == ' ' || *line == '\t')
line++;
}
while (*line);
/* No leave_cmd() here because errors are expected and would clutter
the log. */
return gpg_error (GPG_ERR_NO_SECKEY);
}
static const char hlp_sigkey[] =
"SIGKEY <hexstring_with_keygrip>\n"
"SETKEY <hexstring_with_keygrip>\n"
"\n"
"Set the key used for a sign or decrypt operation.";
static gpg_error_t
cmd_sigkey (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = parse_keygrip (ctx, line, ctrl->keygrip);
if (rc)
return rc;
ctrl->have_keygrip = 1;
return 0;
}
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
"Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
"or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
- "this description implictly selects the label used for the entry\n"
+ "this description implicitly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
"not be translated), \"PIN\" is used, otherwise the translation of\n"
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
"The description is only valid for the next PKSIGN, PKDECRYPT,\n"
"IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *desc, *p;
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage; we might late use it for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
plus_to_blank (desc);
xfree (ctrl->server_local->keydesc);
if (ctrl->restricted)
{
ctrl->server_local->keydesc = strconcat
((ctrl->restricted == 2
? _("Note: Request from the web browser.")
: _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
}
else
ctrl->server_local->keydesc = xtrystrdup (desc);
if (!ctrl->server_local->keydesc)
return out_of_core ();
return 0;
}
static const char hlp_sethash[] =
"SETHASH (--hash=<name>)|(<algonumber>) <hexstring>\n"
"\n"
"The client can use this command to tell the server about the data\n"
"(which usually is a hash) to be signed.";
static gpg_error_t
cmd_sethash (assuan_context_t ctx, char *line)
{
int rc;
size_t n;
char *p;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *buf;
char *endp;
int algo;
/* Parse the alternative hash options which may be used instead of
the algo number. */
if (has_option_name (line, "--hash"))
{
if (has_option (line, "--hash=sha1"))
algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=rmd160"))
algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=md5"))
algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=tls-md5sha1"))
algo = MD_USER_TLS_MD5SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
}
else
algo = 0;
line = skip_options (line);
if (!algo)
{
/* No hash option has been given: require an algo number instead */
algo = (int)strtoul (line, &endp, 10);
for (line = endp; *line == ' ' || *line == '\t'; line++)
;
if (!algo || gcry_md_test_algo (algo))
return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
}
ctrl->digest.algo = algo;
ctrl->digest.raw_value = 0;
/* Parse the hash value. */
n = 0;
rc = parse_hexstring (ctx, line, &n);
if (rc)
return rc;
n /= 2;
if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
;
else if (n != 16 && n != 20 && n != 24
&& n != 28 && n != 32 && n != 48 && n != 64)
return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
if (n > MAX_DIGEST_LEN)
return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
buf = ctrl->digest.value;
ctrl->digest.valuelen = n;
for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
buf[n] = xtoi_2 (p);
for (; n < ctrl->digest.valuelen; n++)
buf[n] = 0;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [<options>] [<cache_nonce>]\n"
"\n"
"Perform the actual sign operation. Neither input nor output are\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
gpg_error_t err;
cache_mode_t cache_mode = CACHE_MODE_NORMAL;
ctrl_t ctrl = assuan_get_pointer (ctx);
membuf_t outbuf;
char *cache_nonce = NULL;
char *p;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
if (opt.ignore_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
else if (!ctrl->server_local->use_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
init_membuf (&outbuf, 512);
err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
&outbuf, cache_mode);
if (err)
clear_outbuf (&outbuf);
else
err = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT [<options>]\n"
"\n"
"Perform the actual decrypt operation. Input is not\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value;
size_t valuelen;
membuf_t outbuf;
int padding;
(void)line;
/* First inquire the data to decrypt */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
if (!rc)
rc = assuan_inquire (ctx, "CIPHERTEXT",
&value, &valuelen, MAXLEN_CIPHERTEXT);
if (rc)
return rc;
init_membuf (&outbuf, 512);
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
value, valuelen, &outbuf, &padding);
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
{
if (padding != -1)
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
else
rc = 0;
if (!rc)
rc = write_and_clear_outbuf (ctx, &outbuf);
}
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, rc);
}
static const char hlp_genkey[] =
"GENKEY [--no-protection] [--preset] [--inq-passwd]\n"
" [--passwd-nonce=<s>] [<cache_nonce>]\n"
"\n"
"Generate a new key, store the secret part and return the public\n"
"part. Here is an example transaction:\n"
"\n"
" C: GENKEY\n"
" S: INQUIRE KEYPARAM\n"
" C: D (genkey (rsa (nbits 3072)))\n"
" C: END\n"
" S: D (public-key\n"
" S: D (rsa (n 326487324683264) (e 10001)))\n"
" S: OK key created\n"
"\n"
"When the --preset option is used the passphrase for the generated\n"
"key will be added to the cache. When --inq-passwd is used an inquire\n"
"with the keyword NEWPASSWD is used to request the passphrase for the\n"
"new key. When a --passwd-nonce is used, the corresponding cached\n"
"passphrase is used to protect the new key.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int no_protection;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *newpasswd = NULL;
membuf_t outbuf;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int opt_preset;
int opt_inq_passwd;
size_t n;
char *p, *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
no_protection = has_option (line, "--no-protection");
opt_preset = has_option (line, "--preset");
opt_inq_passwd = has_option (line, "--inq-passwd");
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
/* First inquire the parameters */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
if (!rc)
rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
if (rc)
return rc;
init_membuf (&outbuf, 512);
/* If requested, ask for the password to be used for the key. If
this is not used the regular Pinentry mechanism is used. */
if (opt_inq_passwd && !no_protection)
{
/* (N is used as a dummy) */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
assuan_end_confidential (ctx);
if (rc)
goto leave;
if (!*newpasswd)
{
/* Empty password given - switch to no-protection mode. */
xfree (newpasswd);
newpasswd = NULL;
no_protection = 1;
}
}
else if (passwd_nonce)
newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection,
newpasswd, opt_preset, &outbuf);
leave:
if (newpasswd)
{
/* Assuan_inquire does not allow us to read into secure memory
thus we need to wipe it ourself. */
wipememory (newpasswd, strlen (newpasswd));
xfree (newpasswd);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
rc = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, rc);
}
static const char hlp_readkey[] =
"READKEY <hexstring_with_keygrip>\n"
" --card <keyid>\n"
"\n"
"Return the public key for the given keygrip or keyid.\n"
"With --card, private key file with card information will be created.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char grip[20];
gcry_sexp_t s_pkey = NULL;
unsigned char *pkbuf = NULL;
char *serialno = NULL;
size_t pkbuflen;
const char *opt_card;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_card = has_option_name (line, "--card");
line = skip_options (line);
if (opt_card)
{
const char *keyid = opt_card;
rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL);
if (rc)
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (rc));
goto leave;
}
rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL);
if (rc)
goto leave;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (rc)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
rc = gcry_pk_testkey (s_pkey);
if (rc == 0)
rc = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0);
if (rc)
goto leave;
rc = assuan_send_data (ctx, pkbuf, pkbuflen);
}
else
{
rc = parse_keygrip (ctx, line, grip);
if (rc)
goto leave;
rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
if (!rc)
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (pkbuflen);
pkbuf = xtrymalloc (pkbuflen);
if (!pkbuf)
rc = gpg_error_from_syserror ();
else
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
pkbuf, pkbuflen);
rc = assuan_send_data (ctx, pkbuf, pkbuflen);
}
}
}
leave:
xfree (serialno);
xfree (pkbuf);
gcry_sexp_release (s_pkey);
return leave_cmd (ctx, rc);
}
static const char hlp_keyinfo[] =
"KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. If --ssh-list is given information\n"
"about all keys listed in the sshcontrol are returned. With --with-ssh\n"
"information from sshcontrol is always added to the info. Unless --data\n"
"is given, the information is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"TYPE is describes the type of the key:\n"
" 'D' - Regular key stored on disk,\n"
" 'T' - Key is stored on a smartcard (token),\n"
" 'X' - Unknown type,\n"
" '-' - Key is missing.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"CACHED is 1 if the passphrase for the key was found in the key cache.\n"
" If not, a '-' is used instead.\n"
"\n"
"PROTECTION describes the key protection type:\n"
" 'P' - The key is protected with a passphrase,\n"
" 'C' - The key is not protected,\n"
" '-' - Unknown protection.\n"
"\n"
"FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
" printed if the option --ssh-fpr has been used. If ALGO is not given\n"
" to that option the default ssh fingerprint algo is used. Without the\n"
" option a '-' is printed.\n"
"\n"
"TTL is the TTL in seconds for that key or '-' if n/a.\n"
"\n"
"FLAGS is a word consisting of one-letter flags:\n"
" 'D' - The key has been disabled,\n"
" 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
" 'c' - Use of the key needs to be confirmed,\n"
" 'A' - The key is available on card,\n"
" '-' - No flags given.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
int data, int with_ssh_fpr, int in_ssh,
int ttl, int disabled, int confirm, int on_card)
{
gpg_error_t err;
char hexgrip[40+1];
char *fpr = NULL;
int keytype;
unsigned char *shadow_info = NULL;
char *serialno = NULL;
char *idstr = NULL;
const char *keytypestr;
const char *cached;
const char *protectionstr;
char *pw;
int missing_key = 0;
char ttlbuf[20];
char flagsbuf[5];
err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
if (err)
{
if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
missing_key = 1;
else
goto leave;
}
/* Reformat the grip so that we use uppercase as good style. */
bin2hex (grip, 20, hexgrip);
if (ttl > 0)
snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
else
strcpy (ttlbuf, "-");
*flagsbuf = 0;
if (disabled)
strcat (flagsbuf, "D");
if (in_ssh)
strcat (flagsbuf, "S");
if (confirm)
strcat (flagsbuf, "c");
if (on_card)
strcat (flagsbuf, "A");
if (!*flagsbuf)
strcpy (flagsbuf, "-");
if (missing_key)
{
protectionstr = "-"; keytypestr = "-";
}
else
{
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
protectionstr = "C"; keytypestr = "D";
break;
case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
break;
case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
break;
default: protectionstr = "-"; keytypestr = "X";
break;
}
}
/* Compute the ssh fingerprint if requested. */
if (with_ssh_fpr)
{
gcry_sexp_t key;
if (!agent_raw_key_from_file (ctrl, grip, &key))
{
ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
gcry_sexp_release (key);
}
}
/* Here we have a little race by doing the cache check separately
from the retrieval function. Given that the cache flag is only a
hint, it should not really matter. */
pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
cached = pw ? "1" : "-";
xfree (pw);
if (shadow_info)
{
err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
if (err)
goto leave;
}
if (!data)
err = agent_write_status (ctrl, "KEYINFO",
hexgrip,
keytypestr,
serialno? serialno : "-",
idstr? idstr : "-",
cached,
protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf,
NULL);
else
{
char *string;
string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
hexgrip, keytypestr,
serialno? serialno : "-",
idstr? idstr : "-", cached, protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf);
if (!string)
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, string, strlen(string));
xfree (string);
}
leave:
xfree (fpr);
xfree (shadow_info);
xfree (serialno);
xfree (idstr);
return err;
}
/* Entry into the command KEYINFO. This function handles the
* command option processing. For details see hlp_keyinfo above. */
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int err;
unsigned char grip[20];
DIR *dir = NULL;
int list_mode;
int opt_data, opt_ssh_fpr, opt_with_ssh;
ssh_control_file_t cf = NULL;
char hexgrip[41];
int disabled, ttl, confirm, is_ssh;
struct card_key_info_s *keyinfo_on_cards;
struct card_key_info_s *l;
int on_card;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--ssh-list"))
list_mode = 2;
else
list_mode = has_option (line, "--list");
opt_data = has_option (line, "--data");
if (has_option_name (line, "--ssh-fpr"))
{
if (has_option (line, "--ssh-fpr=md5"))
opt_ssh_fpr = GCRY_MD_MD5;
else if (has_option (line, "--ssh-fpr=sha1"))
opt_ssh_fpr = GCRY_MD_SHA1;
else if (has_option (line, "--ssh-fpr=sha256"))
opt_ssh_fpr = GCRY_MD_SHA256;
else
opt_ssh_fpr = opt.ssh_fingerprint_digest;
}
else
opt_ssh_fpr = 0;
opt_with_ssh = has_option (line, "--with-ssh");
line = skip_options (line);
if (opt_with_ssh || list_mode == 2)
cf = ssh_open_control_file ();
/* Take the keyinfo for cards from our local cache. Actually this
* cache could be a global one but then we would need to employ
* reference counting. */
if (ctrl->server_local->last_card_keyinfo.ki
&& ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card
&& (ctrl->server_local->last_card_keyinfo.maybe_key_change
== eventcounter.maybe_key_change))
{
keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki;
}
else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards))
{
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards;
ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card;
ctrl->server_local->last_card_keyinfo.maybe_key_change
= eventcounter.maybe_key_change;
}
if (list_mode == 2)
{
if (cf)
{
while (!ssh_read_control_file (cf, hexgrip,
&disabled, &ttl, &confirm))
{
if (hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
ttl, disabled, confirm, on_card);
if (err)
goto leave;
}
}
err = 0;
}
else if (list_mode)
{
char *dirname;
struct dirent *dir_entry;
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
goto leave;
}
xfree (dirname);
while ( (dir_entry = readdir (dir)) )
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, hexgrip,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card);
if (err)
goto leave;
}
err = 0;
}
else
{
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, line,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, line, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card);
}
leave:
ssh_close_control_file (cf);
if (dir)
closedir (dir);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
leave_cmd (ctx, err);
return err;
}
/* Helper for cmd_get_passphrase. */
static int
send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
{
size_t n;
int rc;
assuan_begin_confidential (ctx);
n = strlen (pw);
if (via_data)
rc = assuan_send_data (ctx, pw, n);
else
{
char *p = xtrymalloc_secure (n*2+1);
if (!p)
rc = gpg_error_from_syserror ();
else
{
bin2hex (pw, n, p);
rc = assuan_set_okay_line (ctx, p);
xfree (p);
}
}
return rc;
}
static const char hlp_get_passphrase[] =
"GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
" [--qualitybar] <cache_id>\n"
" [<error_message> <prompt> <description>]\n"
"\n"
"This function is usually used to ask for a passphrase to be used\n"
"for conventional encryption, but may also be used by programs which\n"
"need specal handling of passphrases. This command uses a syntax\n"
"which helps clients to use the agent with minimum effort. The\n"
"agent either returns with an error or with a OK followed by the hex\n"
"encoded passphrase. Note that the length of the strings is\n"
"implicitly limited by the maximum length of a command.\n"
"\n"
"If the option \"--data\" is used the passphrase is returned by usual\n"
"data lines and not on the okay line.\n"
"\n"
"If the option \"--check\" is used the passphrase constraints checks as\n"
"implemented by gpg-agent are applied. A check is not done if the\n"
"passphrase has been found in the cache.\n"
"\n"
"If the option \"--no-ask\" is used and the passphrase is not in the\n"
"cache the user will not be asked to enter a passphrase but the error\n"
"code GPG_ERR_NO_DATA is returned. \n"
"\n"
"If the option \"--qualitybar\" is used a visual indication of the\n"
"entered passphrase quality is shown. (Unless no minimum passphrase\n"
"length has been configured.)";
static gpg_error_t
cmd_get_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *pw;
char *response;
char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL;
const char *desc2 = _("Please re-enter this passphrase");
char *p;
int opt_data, opt_check, opt_no_ask, opt_qualbar;
int opt_repeat = 0;
char *entry_errtext = NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_data = has_option (line, "--data");
opt_check = has_option (line, "--check");
opt_no_ask = has_option (line, "--no-ask");
if (has_option_name (line, "--repeat"))
{
p = option_value (line, "--repeat");
if (p)
opt_repeat = atoi (p);
else
opt_repeat = 1;
}
opt_qualbar = has_option (line, "--qualitybar");
line = skip_options (line);
cacheid = line;
p = strchr (cacheid, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
errtext = p;
p = strchr (errtext, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
prompt = p;
p = strchr (prompt, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* Ignore trailing garbage. */
}
}
}
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
if (!desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (cacheid, "X"))
cacheid = NULL;
if (!strcmp (errtext, "X"))
errtext = NULL;
if (!strcmp (prompt, "X"))
prompt = NULL;
if (!strcmp (desc, "X"))
desc = NULL;
pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
if (pw)
{
rc = send_back_passphrase (ctx, opt_data, pw);
xfree (pw);
}
else if (opt_no_ask)
rc = gpg_error (GPG_ERR_NO_DATA);
else
{
/* Note, that we only need to replace the + characters and
should leave the other escaping in place because the escaped
string is send verbatim to the pinentry which does the
unescaping (but not the + replacing) */
if (errtext)
plus_to_blank (errtext);
if (prompt)
plus_to_blank (prompt);
if (desc)
plus_to_blank (desc);
next_try:
rc = agent_get_passphrase (ctrl, &response, desc, prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER);
xfree (entry_errtext);
entry_errtext = NULL;
if (!rc)
{
int i;
if (opt_check
&& check_passphrase_constraints (ctrl, response, &entry_errtext))
{
xfree (response);
goto next_try;
}
for (i = 0; i < opt_repeat; i++)
{
char *response2;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
break;
rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
errtext, 0,
cacheid, CACHE_MODE_USER);
if (rc)
break;
if (strcmp (response2, response))
{
xfree (response2);
xfree (response);
entry_errtext = try_percent_escape
(_("does not match - try again"), NULL);
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
break;
}
goto next_try;
}
xfree (response2);
}
if (!rc)
{
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
rc = send_back_passphrase (ctx, opt_data, response);
}
xfree (response);
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_clear_passphrase[] =
"CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
"\n"
"may be used to invalidate the cache entry for a passphrase. The\n"
"function returns with OK even when there is no cached passphrase.\n"
"The --mode=normal option is used to clear an entry for a cacheid\n"
"added by the agent. The --mode=ssh option is used for a cacheid\n"
"added for ssh.\n";
static gpg_error_t
cmd_clear_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *cacheid = NULL;
char *p;
cache_mode_t cache_mode = CACHE_MODE_USER;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--mode=normal"))
cache_mode = CACHE_MODE_NORMAL;
else if (has_option (line, "--mode=ssh"))
cache_mode = CACHE_MODE_SSH;
line = skip_options (line);
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
cacheid = p;
p = strchr (cacheid, ' ');
if (p)
*p = 0; /* ignore garbage */
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
agent_clear_passphrase (ctrl, cacheid, cache_mode);
return 0;
}
static const char hlp_get_confirmation[] =
"GET_CONFIRMATION <description>\n"
"\n"
"This command may be used to ask for a simple confirmation.\n"
"DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
"command uses a syntax which helps clients to use the agent with\n"
"minimum effort. The agent either returns with an error or with a\n"
"OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
"the maximum length of a command. DESCRIPTION should not contain\n"
"any spaces, those must be encoded either percent escaped or simply\n"
"as '+'.";
static gpg_error_t
cmd_get_confirmation (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *desc = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage -may be later used for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (desc, "X"))
desc = NULL;
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
if (desc)
plus_to_blank (desc);
rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
return leave_cmd (ctx, rc);
}
static const char hlp_learn[] =
"LEARN [--send] [--sendinfo] [--force]\n"
"\n"
"Learn something about the currently inserted smartcard. With\n"
"--sendinfo information about the card is returned; with --send\n"
"the available certificates are returned as D lines; with --force\n"
"private key storage will be updated by the result.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int send, sendinfo, force;
send = has_option (line, "--send");
sendinfo = send? 1 : has_option (line, "--sendinfo");
force = has_option (line, "--force");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
return leave_cmd (ctx, err);
}
static const char hlp_passwd[] =
"PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
" [--verify] <hexkeygrip>\n"
"\n"
"Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
"--preset is used then the new passphrase will be added to the cache.\n"
"If --verify is used the command asks for the passphrase and verifies\n"
"that the passphrase valid.\n";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int c;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *shadow_info = NULL;
char *passphrase = NULL;
char *pend;
int opt_preset, opt_verify;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_preset = has_option (line, "--preset");
cache_nonce = option_value (line, "--cache-nonce");
opt_verify = has_option (line, "--verify");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
ctrl->in_passwd++;
err = agent_key_from_file (ctrl,
opt_verify? NULL : cache_nonce,
ctrl->server_local->keydesc,
grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, &passphrase);
if (err)
;
else if (shadow_info)
{
log_error ("changing a smartcard PIN is not yet supported\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else if (opt_verify)
{
/* All done. */
if (passphrase)
{
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
else
{
char *newpass = NULL;
if (passwd_nonce)
newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
err = agent_protect_and_store (ctrl, s_skey, &newpass);
if (!err && passphrase)
{
/* A passphrase existed on the old key and the change was
successful. Return a nonce for that old passphrase to
let the caller try to unprotect the other subkeys with
the same key. */
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
if (newpass)
{
/* If we have a new passphrase (which might be empty) we
store it under a passwd nonce so that the caller may
send that nonce again to use it for another key. */
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
newpass, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
if (!err && opt_preset)
{
char hexgrip[40+1];
bin2hex(grip, 20, hexgrip);
err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
ctrl->cache_ttl_opt_preset);
}
xfree (newpass);
}
ctrl->in_passwd--;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
leave:
xfree (passphrase);
gcry_sexp_release (s_skey);
xfree (shadow_info);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, err);
}
static const char hlp_preset_passphrase[] =
"PRESET_PASSPHRASE [--inquire] <string_or_keygrip> <timeout> [<hexstring>]\n"
"\n"
"Set the cached passphrase/PIN for the key identified by the keygrip\n"
"to passwd for the given time, where -1 means infinite and 0 means\n"
"the default (currently only a timeout of -1 is allowed, which means\n"
"to never expire it). If passwd is not provided, ask for it via the\n"
"pinentry module unless --inquire is passed in which case the passphrase\n"
"is retrieved from the client via a server inquire.\n";
static gpg_error_t
cmd_preset_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *grip_clear = NULL;
unsigned char *passphrase = NULL;
int ttl;
size_t len;
int opt_inquire;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!opt.allow_preset_passphrase)
return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
opt_inquire = has_option (line, "--inquire");
line = skip_options (line);
grip_clear = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
return gpg_error (GPG_ERR_MISSING_VALUE);
*line = '\0';
line++;
while (*line && (*line == ' ' || *line == '\t'))
line++;
/* Currently, only infinite timeouts are allowed. */
ttl = -1;
if (line[0] != '-' || line[1] != '1')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
line++;
line++;
while (!(*line != ' ' && *line != '\t'))
line++;
/* Syntax check the hexstring. */
len = 0;
rc = parse_hexstring (ctx, line, &len);
if (rc)
return rc;
line[len] = '\0';
/* If there is a passphrase, use it. Currently, a passphrase is
required. */
if (*line)
{
if (opt_inquire)
{
rc = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and passphrase specified");
goto leave;
}
/* Do in-place conversion. */
passphrase = line;
if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
}
else if (opt_inquire)
{
/* Note that the passphrase will be truncated at any null byte and the
* limit is 480 characters. */
size_t maxlen = 480;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!rc)
rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
}
else
rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
if (!rc)
{
rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
if (opt_inquire)
xfree (passphrase);
}
leave:
return leave_cmd (ctx, rc);
}
static const char hlp_scd[] =
"SCD <commands to pass to the scdaemon>\n"
" \n"
"This is a general quote command to redirect everything to the\n"
"SCdaemon.";
static gpg_error_t
cmd_scd (assuan_context_t ctx, char *line)
{
int rc;
#ifdef BUILD_WITH_SCDAEMON
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* All SCD prefixed commands may change a key. */
eventcounter.maybe_key_change++;
rc = divert_generic_cmd (ctrl, line, ctx);
#else
(void)ctx; (void)line;
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif
return rc;
}
static const char hlp_keywrap_key[] =
"KEYWRAP_KEY [--clear] <mode>\n"
"\n"
"Return a key to wrap another key. For now the key is returned\n"
"verbatim and thus makes not much sense because an eavesdropper on\n"
"the gpg-agent connection will see the key as well as the wrapped key.\n"
"However, this function may either be equipped with a public key\n"
"mechanism or not used at all if the key is a pre-shared key. In any\n"
"case wrapping the import and export of keys is a requirement for\n"
"certain cryptographic validations and thus useful. The key persists\n"
"until a RESET command but may be cleared using the option --clear.\n"
"\n"
"Supported modes are:\n"
" --import - Return a key to import a key into gpg-agent\n"
" --export - Return a key to export a key from gpg-agent";
static gpg_error_t
cmd_keywrap_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clearopt = has_option (line, "--clear");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
assuan_begin_confidential (ctx);
if (has_option (line, "--import"))
{
xfree (ctrl->server_local->import_key);
if (clearopt)
ctrl->server_local->import_key = NULL;
else if (!(ctrl->server_local->import_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->import_key,
KEYWRAP_KEYSIZE);
}
else if (has_option (line, "--export"))
{
xfree (ctrl->server_local->export_key);
if (clearopt)
ctrl->server_local->export_key = NULL;
else if (!(ctrl->server_local->export_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->export_key,
KEYWRAP_KEYSIZE);
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
assuan_end_confidential (ctx);
return leave_cmd (ctx, err);
}
static const char hlp_import_key[] =
"IMPORT_KEY [--unattended] [--force] [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption. Existing key can be overwritten with --force.";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
int force;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *key = NULL;
size_t keylen, realkeylen;
char *passphrase = NULL;
unsigned char *finalkey = NULL;
size_t finalkeylen;
unsigned char grip[20];
gcry_sexp_t openpgp_sexp = NULL;
char *cache_nonce = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!ctrl->server_local->import_key)
{
err = gpg_error (GPG_ERR_MISSING_KEY);
goto leave;
}
opt_unattended = has_option (line, "--unattended");
force = has_option (line, "--force");
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYDATA",
&wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (err)
goto leave;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
xfree (wrappedkey);
wrappedkey = NULL;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
err = keygrip_from_canon_sexp (key, realkeylen, grip);
if (err)
{
/* This might be due to an unsupported S-expression format.
Check whether this is openpgp-private-key and trigger that
import code. */
if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
{
const char *tag;
size_t taglen;
tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
;
else
{
gcry_sexp_release (openpgp_sexp);
openpgp_sexp = NULL;
}
}
if (!openpgp_sexp)
goto leave; /* Note that ERR is still set. */
}
if (openpgp_sexp)
{
/* In most cases the key is encrypted and thus the conversion
function from the OpenPGP format to our internal format will
ask for a passphrase. That passphrase will be returned and
used to protect the key using the same code as for regular
key import. */
xfree (key);
key = NULL;
err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
ctrl->server_local->keydesc, cache_nonce,
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
log_assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
else if (opt_unattended)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"\"--unattended\" may only be used with OpenPGP keys");
goto leave;
}
else
{
if (!force && !agent_key_available (grip))
err = gpg_error (GPG_ERR_EEXIST);
else
{
char *prompt = xtryasprintf
(_("Please enter the passphrase to protect the "
"imported object within the %s system."), GNUPG_NAME);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
xfree (prompt);
}
if (err)
goto leave;
}
if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count, -1);
if (!err)
err = agent_write_private_key (grip, finalkey, finalkeylen, force,
NULL, NULL);
}
else
err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL);
leave:
gcry_sexp_release (openpgp_sexp);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_export_key[] =
"EXPORT_KEY [--cache-nonce=<nonce>] [--openpgp] <hexstring_with_keygrip>\n"
"\n"
"Export a secret key from the key store. The key will be encrypted\n"
"using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
"using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
"prior to using this command. The function takes the keygrip as argument.\n"
"\n"
"If --openpgp is used, the secret key material will be exported in RFC 4880\n"
"compatible passphrase-protected form. Without --openpgp, the secret key\n"
"material will be exported in the clear (after prompting the user to unlock\n"
"it, if needed).\n";
static gpg_error_t
cmd_export_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *key = NULL;
size_t keylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
int openpgp;
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
char *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
openpgp = has_option (line, "--openpgp");
cache_nonce = option_value (line, "--cache-nonce");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
if (!ctrl->server_local->export_key)
{
err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
goto leave;
}
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Get the key from the file. With the openpgp flag we also ask for
the passphrase so that we can use it to re-encrypt it. */
err = agent_key_from_file (ctrl, cache_nonce,
ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
openpgp ? &passphrase : NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is on a smartcard. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
if (openpgp)
{
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
if (!passphrase)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
" Please enter a new passphrase to export it."),
&passphrase);
if (err)
goto leave;
}
err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
}
}
else
{
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
if (err)
goto leave;
gcry_sexp_release (s_skey);
s_skey = NULL;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
assuan_end_confidential (ctx);
leave:
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
xfree (key);
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_delete_key[] =
"DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
"\n"
"Delete a secret key from the key store. If --force is used\n"
"and a loopback pinentry is allowed, the agent will not ask\n"
"the user for confirmation. If --stub-only is used the key will\n"
"only be deleted if it is a reference to a token.";
static gpg_error_t
cmd_delete_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int force, stub_only;
unsigned char grip[20];
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
stub_only = has_option (line, "--stub-only");
line = skip_options (line);
eventcounter.maybe_key_change++;
/* If the use of a loopback pinentry has been disabled, we assume
* that a silent deletion of keys shall also not be allowed. */
if (!opt.allow_loopback_pinentry)
force = 0;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
force, stub_only);
if (err)
goto leave;
leave:
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
#else
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
#endif
static const char hlp_keytocard[] =
"KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp>]\n"
"\n"
"TIMESTAMP is required for OpenPGP and defaults to the Epoch. The\n"
"SERIALNO is used for checking; use \"-\" to disable the check.";
static gpg_error_t
cmd_keytocard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int force;
gpg_error_t err = 0;
char *argv[5];
int argc;
unsigned char grip[20];
const char *serialno, *timestamp_str, *keyref;
gcry_sexp_t s_skey = NULL;
unsigned char *keydata;
size_t keydatalen;
unsigned char *shadow_info = NULL;
time_t timestamp;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 3)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (agent_key_available (grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Note that checking of the s/n is currently not implemented but we
* want to provide a clean interface if we ever implement it. */
serialno = argv[1];
if (!strcmp (serialno, "-"))
serialno = NULL;
keyref = argv[2];
/* FIXME: Default to the creation time as stored in the private
* key. The parameter is here so that gpg can make sure that the
* timestamp as used for key creation (and thus the openPGP
* fingerprint) is used. */
timestamp_str = argc > 3? argv[3] : "19700101T000000";
if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1))
{
err = gpg_error (GPG_ERR_INV_TIME);
goto leave;
}
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is already on a smartcard - we can't extract it. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
/* Note: We can't use make_canon_sexp because we need to allocate a
* few extra bytes for our hack below. */
keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
keydata = xtrymalloc_secure (keydatalen + 30);
if (keydata == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
gcry_sexp_release (s_skey);
s_skey = NULL;
keydatalen--; /* Decrement for last '\0'. */
/* Hack to insert the timestamp "created-at" into the private key. */
snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp);
keydatalen += 10 + 19 - 1;
err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen);
xfree (keydata);
leave:
gcry_sexp_release (s_skey);
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_get_secret[] =
"GET_SECRET <key>\n"
"\n"
"Return the secret value stored under KEY\n";
static gpg_error_t
cmd_get_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *key;
char *value = NULL;
size_t valuelen;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
goto leave;
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
value = agent_get_cache (ctrl, key, CACHE_MODE_DATA);
if (!value)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
valuelen = percent_unescape_inplace (value, 0);
err = assuan_send_data (ctx, value, valuelen);
wipememory (value, valuelen);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_put_secret[] =
"PUT_SECRET [--clear] <key> <ttl> [<percent_escaped_value>]\n"
"\n"
"This commands stores a secret under KEY in gpg-agent's in-memory\n"
"cache. The TTL must be explicitly given by TTL and the options\n"
"from the configuration file are not used. The value is either given\n"
"percent-escaped as 3rd argument or if not given inquired by gpg-agent\n"
"using the keyword \"SECRET\".\n"
"The option --clear removes the secret from the cache."
"";
static gpg_error_t
cmd_put_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int opt_clear;
unsigned char *value = NULL;
size_t valuelen = 0;
size_t n;
char *p, *key, *ttlstr;
unsigned char *valstr;
int ttl;
char *string = NULL;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
opt_clear = has_option (line, "--clear");
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
ttlstr = NULL;
valstr = NULL;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
ttlstr = p;
p = strchr (ttlstr, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
valstr = p;
}
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl)))
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given");
goto leave;
}
if (valstr && opt_clear)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"value not expected with --clear");
goto leave;
}
if (valstr)
{
valuelen = percent_unescape_inplace (valstr, 0);
value = NULL;
}
else /* Inquire the value to store */
{
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET);
if (!err)
err = assuan_inquire (ctx, "SECRET",
&value, &valuelen, MAXLEN_PUT_SECRET);
if (err)
goto leave;
}
/* Our cache expects strings and thus we need to turn the buffer
* into a string. Instead of resorting to base64 encoding we use a
* special percent escaping which only quoted the Nul and the
* percent character. */
string = percent_data_escape (0, NULL, value? value : valstr, valuelen);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl);
leave:
if (string)
{
wipememory (string, strlen (string));
xfree (string);
}
if (value)
{
wipememory (value, valuelen);
xfree (value);
}
return leave_cmd (ctx, err);
}
static const char hlp_getval[] =
"GETVAL <key>\n"
"\n"
"Return the value for KEY from the special environment as created by\n"
"PUTVAL.";
static gpg_error_t
cmd_getval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *p;
struct putval_item_s *vl;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list; vl; vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Got an entry. */
rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
else
return gpg_error (GPG_ERR_NO_DATA);
return leave_cmd (ctx, rc);
}
static const char hlp_putval[] =
"PUTVAL <key> [<percent_escaped_value>]\n"
"\n"
"The gpg-agent maintains a kind of environment which may be used to\n"
"store key/value pairs in it, so that they can be retrieved later.\n"
"This may be used by helper daemons to daemonize themself on\n"
"invocation and register them with gpg-agent. Callers of the\n"
"daemon's service may now first try connect to get the information\n"
"for that service from gpg-agent through the GETVAL command and then\n"
"try to connect to that daemon. Only if that fails they may start\n"
"an own instance of the service daemon. \n"
"\n"
"KEY is an arbitrary symbol with the same syntax rules as keys\n"
"for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
"corresponding value; they should be similar to the values of\n"
"envronment variables but gpg-agent does not enforce any\n"
"restrictions. If that value is not given any value under that KEY\n"
"is removed from this special environment.";
static gpg_error_t
cmd_putval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *value = NULL;
size_t valuelen = 0;
char *p;
struct putval_item_s *vl, *vlprev;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
value = p;
p = strchr (value, ' ');
if (p)
*p = 0;
valuelen = percent_plus_unescape_inplace (value, 0);
}
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Delete old entry. */
{
if (vlprev)
vlprev->next = vl->next;
else
putval_list = vl->next;
xfree (vl);
}
if (valuelen) /* Add entry. */
{
vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
if (!vl)
rc = gpg_error_from_syserror ();
else
{
vl->len = valuelen;
vl->off = strlen (key) + 1;
strcpy (vl->d, key);
memcpy (vl->d + vl->off, value, valuelen);
vl->next = putval_list;
putval_list = vl;
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_updatestartuptty[] =
"UPDATESTARTUPTTY\n"
"\n"
"Set startup TTY and X11 DISPLAY variables to the values of this\n"
"session. This command is useful to pull future pinentries to\n"
"another screen. It is only required because there is no way in the\n"
"ssh-agent protocol to convey this information.";
static gpg_error_t
cmd_updatestartuptty (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
session_env_t se;
char *lc_ctype = NULL;
char *lc_messages = NULL;
int iterator;
const char *name;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
se = session_env_new ();
if (!se)
err = gpg_error_from_syserror ();
iterator = 0;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
const char *value = session_env_getenv (ctrl->session_env, name);
if (value)
err = session_env_setenv (se, name, value);
}
if (!err && ctrl->lc_ctype)
if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && ctrl->lc_messages)
if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
err = gpg_error_from_syserror ();
if (err)
{
session_env_release (se);
xfree (lc_ctype);
xfree (lc_messages);
}
else
{
session_env_release (opt.startup_env);
opt.startup_env = se;
xfree (opt.startup_lc_ctype);
opt.startup_lc_ctype = lc_ctype;
xfree (opt.startup_lc_messages);
opt.startup_lc_messages = lc_messages;
}
return err;
}
static const char hlp_killagent[] =
"KILLAGENT\n"
"\n"
"Stop the agent.";
static gpg_error_t
cmd_killagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadagent[] =
"RELOADAGENT\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
agent_sighup_action ();
return 0;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" ssh_socket_name - Return the name of the ssh socket.\n"
" scd_running - Return OK if the SCdaemon is already running.\n"
" s2k_time - Return the time in milliseconds required for S2K.\n"
" s2k_count - Return the standard S2K count.\n"
" s2k_count_cal - Return the calibrated S2K count.\n"
" std_env_names - List the names of the standard environment.\n"
" std_session_env - List the standard session environment.\n"
" std_startup_env - List the standard startup environment.\n"
" getenv NAME - Return value of envvar NAME.\n"
" connections - Return number of active connections.\n"
" jent_active - Returns OK if Libgcrypt's JENT is active.\n"
" restricted - Returns OK if the connection is in restricted mode.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "s2k_count"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "restricted"))
{
rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (ctrl->restricted)
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All sub-commands below are not allowed in restricted mode. */
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_agent_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "ssh_socket_name"))
{
const char *s = get_agent_ssh_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "scd_running"))
{
rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "std_env_names"))
{
int iterator;
const char *name;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
rc = assuan_send_data (ctx, name, strlen (name)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
break;
}
}
else if (!strcmp (line, "std_session_env")
|| !strcmp (line, "std_startup_env"))
{
int iterator;
const char *name, *value;
char *string;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
value = session_env_getenv_or_default
(line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
if (value)
{
string = xtryasprintf ("%s=%s", name, value);
if (!string)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_send_data (ctx, string, strlen (string)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (rc)
break;
}
}
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
rc = assuan_send_data (ctx, s, strlen (s));
}
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d",
get_agent_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "jent_active"))
{
#if GCRYPT_VERSION_NUMBER >= 0x010800
char *buf;
char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
rc = 0;
else
rc = gpg_error (GPG_ERR_FALSE);
gcry_free (buf);
#else
rc = gpg_error (GPG_ERR_FALSE);
#endif
}
else if (!strcmp (line, "s2k_count_cal"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "s2k_time"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
/* This function is called by Libassuan to parse the OPTION command.
It has been registered similar to the other Assuan commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "agent-awareness"))
{
/* The value is a version string telling us of which agent
version the caller is aware of. */
ctrl->server_local->allow_fully_canceled =
gnupg_compare_version (value, "2.1.0");
}
else if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All options below are not allowed in restricted mode. */
else if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (ctrl->session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = xtrystrdup (value);
if (!ctrl->lc_ctype)
return out_of_core ();
}
else if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "use-cache-for-signing"))
ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
else if (!strcmp (key, "allow-pinentry-notify"))
ctrl->server_local->allow_pinentry_notify = 1;
else if (!strcmp (key, "pinentry-mode"))
{
int tmp = parse_pinentry_mode (value);
if (tmp == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
ctrl->pinentry_mode = tmp;
}
else if (!strcmp (key, "cache-ttl-opt-preset"))
{
ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
}
else if (!strcmp (key, "s2k-count"))
{
ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
if (ctrl->s2k_count && ctrl->s2k_count < 65536)
{
ctrl->s2k_count = 0;
}
}
else if (!strcmp (key, "pretend-request-origin"))
{
log_assert (!ctrl->restricted);
switch (parse_request_origin (value))
{
case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
/* Better pretend to be remote in case of a bad value. */
ctrl->restricted = 1;
break;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* Called by libassuan after all commands. ERR is the error from the
last assuan operation and not the one returned from the command. */
static void
post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)err;
/* Switch off any I/O monitor controlled logging pausing. */
ctrl->server_local->pause_io_logging = 0;
}
/* This function is called by libassuan for all I/O. We use it here
to disable logging for the GETEVENTCOUNTER commands. This is so
that the debug output won't get cluttered by this primitive
command. */
static unsigned int
io_monitor (assuan_context_t ctx, void *hook, int direction,
const char *line, size_t linelen)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) hook;
/* We want to suppress all Assuan log messages for connections from
* self. However, assuan_get_pid works only after
* assuan_accept. Now, assuan_accept already logs a line ending with
* the process id. We use this hack here to get the peers pid so
* that we can compare it to our pid. We should add an assuan
* function to return the pid for a file descriptor and use that to
* detect connections to self. */
if (ctx && !ctrl->server_local->greeting_seen
&& direction == ASSUAN_IO_TO_PEER)
{
ctrl->server_local->greeting_seen = 1;
if (linelen > 32
&& !strncmp (line, "OK Pleased to meet you, process ", 32)
&& strtoul (line+32, NULL, 10) == getpid ())
return ASSUAN_IO_MONITOR_NOLOG;
}
/* Do not log self-connections. This makes the log cleaner because
* we won't see the check-our-own-socket calls. */
if (ctx && ctrl->server_local->connect_from_self)
return ASSUAN_IO_MONITOR_NOLOG;
/* Note that we only check for the uppercase name. This allows the user to
see the logging for debugging if using a non-upercase command
name. */
if (ctx && direction == ASSUAN_IO_FROM_PEER
&& linelen >= 15
&& !strncmp (line, "GETEVENTCOUNTER", 15)
&& (linelen == 15 || spacep (line+15)))
{
ctrl->server_local->pause_io_logging = 1;
}
return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "GET_PASSPHRASE"))
{
if (!strcmp (cmdopt, "repeat"))
return 1;
}
return 0;
}
/* Tell Libassuan about our commands. Also register the other Assuan
handlers. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
{ "ISTRUSTED", cmd_istrusted, hlp_istrusted },
{ "HAVEKEY", cmd_havekey, hlp_havekey },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "SIGKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
{ "SETHASH", cmd_sethash, hlp_sethash },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
{ "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
{ "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
{ "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
{ "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
{ "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
{ "LEARN", cmd_learn, hlp_learn },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "SCD", cmd_scd, hlp_scd },
{ "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
{ "IMPORT_KEY", cmd_import_key, hlp_import_key },
{ "EXPORT_KEY", cmd_export_key, hlp_export_key },
{ "DELETE_KEY", cmd_delete_key, hlp_delete_key },
{ "GET_SECRET", cmd_get_secret, hlp_get_secret },
{ "PUT_SECRET", cmd_put_secret, hlp_put_secret },
{ "GETVAL", cmd_getval, hlp_getval },
{ "PUTVAL", cmd_putval, hlp_putval },
{ "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_register_post_cmd_notify (ctx, post_cmd_notify);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
simple piper server, otherwise it is a regular server. CTRL is the
control structure for this connection; it has only the basic
initialization. */
void
start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
if (ctrl->restricted)
{
if (agent_copy_startup_env (ctrl))
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
agent_exit (2);
}
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else if (listen_fd != GNUPG_INVALID_FD)
{
rc = assuan_init_socket_server (ctx, listen_fd, 0);
/* FIXME: Need to call assuan_sock_set_nonce for Windows. But
this branch is currently not used. */
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->use_cache_for_signing = 1;
ctrl->digest.raw_value = 0;
assuan_set_io_monitor (ctx, io_monitor, NULL);
agent_set_progress_cb (progress_cb, ctrl);
for (;;)
{
assuan_peercred_t client_creds; /* Note: Points into CTX. */
pid_t pid;
rc = assuan_accept (ctx);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_get_peercred (ctx, &client_creds);
if (rc)
{
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
;
else
log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc));
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
}
else
{
#ifdef HAVE_W32_SYSTEM
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
#else
pid = client_creds->pid;
ctrl->client_uid = client_creds->uid;
#endif
}
ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid;
ctrl->server_local->connect_from_self = (pid == getpid ());
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Clear the keyinfo cache. */
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
/* Reset the nonce caches. */
clear_nonce_cache (ctrl);
/* Reset the SCD if needed. */
agent_reset_scd (ctrl);
/* Reset the pinentry (in case of popup messages). */
agent_reset_query (ctrl);
/* Cleanup. */
assuan_release (ctx);
xfree (ctrl->server_local->keydesc);
xfree (ctrl->server_local->import_key);
xfree (ctrl->server_local->export_key);
if (ctrl->server_local->stopme)
agent_exit (0);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
/* Helper for the pinentry loopback mode. It merely passes the
parameters on to the client. */
gpg_error_t
pinentry_loopback(ctrl_t ctrl, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length)
{
gpg_error_t rc;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
if (rc)
return rc;
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
assuan_end_confidential (ctx);
return rc;
}
/* Helper for the pinentry loopback mode to ask confirmation
or just to show message. */
gpg_error_t
pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok)
{
gpg_error_t err = 0;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (desc)
err = print_assuan_status (ctx, "SETDESC", "%s", desc);
if (!err && ok)
err = print_assuan_status (ctx, "SETOK", "%s", ok);
if (!err && notok)
err = print_assuan_status (ctx, "SETNOTOK", "%s", notok);
if (!err)
err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0",
NULL, NULL, 0);
return err;
}
diff --git a/agent/findkey.c b/agent/findkey.c
index 69c90b37f..7fb938b35 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1,1748 +1,1748 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <npth.h> /* (we use pth_sleep) */
#include "agent.h"
#include "../common/i18n.h"
#include "../common/ssh-utils.h"
#include "../common/name-value.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
/* Helper to pass data to the check callback of the unprotect function. */
struct try_unprotect_arg_s
{
ctrl_t ctrl;
const unsigned char *protected_key;
unsigned char *unprotected_key;
int change_required; /* Set by the callback to indicate that the
user should change the passphrase. */
};
/* Repalce all linefeeds in STRING by "%0A" and return a new malloced
* string. May return NULL on memory error. */
static char *
linefeed_to_percent0A (const char *string)
{
const char *s;
size_t n;
char *buf, *p;
for (n=0, s=string; *s; s++)
if (*s == '\n')
n += 3;
else
n++;
p = buf = xtrymalloc (n+1);
if (!buf)
return NULL;
for (s=string; *s; s++)
if (*s == '\n')
{
memcpy (p, "%0A", 3);
p += 3;
}
else
*p++ = *s;
*p = 0;
return buf;
}
/* Note: Ownership of FNAME and FP are moved to this function. */
static gpg_error_t
write_extended_private_key (char *fname, estream_t fp, int update,
const void *buf, size_t len,
const char *serialno, const char *keyref)
{
gpg_error_t err;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
char *token = NULL;
if (update)
{
int line;
err = nvc_parse_private_key (&pk, &line, fp);
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
{
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
goto leave;
}
}
else
{
pk = nvc_new_private_key ();
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
es_clearerr (fp);
err = gcry_sexp_sscan (&key, NULL, buf, len);
if (err)
goto leave;
err = nvc_set_private_key (pk, key);
if (err)
goto leave;
/* If requested write a Token line. */
if (serialno && keyref)
{
nve_t item;
const char *s;
token = strconcat (serialno, " ", keyref, NULL);
if (!token)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* fixme: the strcmp should compare only the first two strings. */
for (item = nvc_lookup (pk, "Token:");
item;
item = nve_next_value (item, "Token:"))
if ((s = nve_value (item)) && !strcmp (s, token))
break;
if (!item)
{
/* No token or no token with that value exists. Add a new
* one so that keys which have been stored on several cards
* are well supported. */
err = nvc_add (pk, "Token:", token);
if (err)
goto leave;
}
}
err = es_fseek (fp, 0, SEEK_SET);
if (err)
goto leave;
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (ftruncate (es_fileno (fp), es_ftello (fp)))
{
err = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
else
fp = NULL;
bump_key_eventcounter ();
leave:
es_fclose (fp);
if (remove)
gnupg_remove (fname);
xfree (fname);
gcry_sexp_release (key);
nvc_release (pk);
xfree (token);
return err;
}
/* Write an S-expression formatted key to our key storage. With FORCE
* passed as true an existing key with the given GRIP will get
- * overwritten. If SERIALNO and KEYREF are give an a Token line is added to
- * th key if the extended format ist used. */
+ * overwritten. If SERIALNO and KEYREF are given a Token line is added to
+ * the key if the extended format is used. */
int
agent_write_private_key (const unsigned char *grip,
const void *buffer, size_t length, int force,
const char *serialno, const char *keyref)
{
char *fname;
estream_t fp;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
/* FIXME: Write to a temp file first so that write failures during
key updates won't lead to a key loss. */
if (!force && !access (fname, F_OK))
{
log_error ("secret key file '%s' already exists\n", fname);
xfree (fname);
return gpg_error (GPG_ERR_EEXIST);
}
fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT)
{
fp = es_fopen (fname, "wbx,mode=-rw");
if (!fp)
tmperr = gpg_error_from_syserror ();
}
if (!fp)
{
log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
xfree (fname);
return tmperr;
}
}
else if (force)
{
gpg_error_t rc;
char first;
/* See if an existing key is in extended format. */
if (es_fread (&first, 1, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
rc = es_fseek (fp, 0, SEEK_SET);
if (rc)
{
log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
if (first != '(')
{
/* Key is already in the extended format. */
return write_extended_private_key (fname, fp, 1, buffer, length,
serialno, keyref);
}
if (first == '(' && opt.enable_extended_key_format)
{
/* Key is in the old format - but we want the extended format. */
return write_extended_private_key (fname, fp, 0, buffer, length,
serialno, keyref);
}
}
if (opt.enable_extended_key_format)
return write_extended_private_key (fname, fp, 0, buffer, length,
serialno, keyref);
if (es_fwrite (buffer, length, 1, fp) != 1)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
/* When force is given, the file might have to be truncated. */
if (force && ftruncate (es_fileno (fp), es_ftello (fp)))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
if (es_fclose (fp))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr));
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
bump_key_eventcounter ();
xfree (fname);
return 0;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_unprotect_cb (struct pin_entry_info_s *pi)
{
struct try_unprotect_arg_s *arg = pi->check_cb_arg;
ctrl_t ctrl = arg->ctrl;
size_t dummy;
gpg_error_t err;
gnupg_isotime_t now, protected_at, tmptime;
char *desc = NULL;
log_assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
&arg->unprotected_key, &dummy);
if (err)
return err;
if (!opt.max_passphrase_days || ctrl->in_passwd)
return 0; /* No regular passphrase change required. */
if (!*protected_at)
{
/* No protection date known - must force passphrase change. */
desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
"Please change it now."));
if (!desc)
return gpg_error_from_syserror ();
}
else
{
gnupg_get_isotime (now);
gnupg_copy_time (tmptime, protected_at);
err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
if (err)
return err;
if (strcmp (now, tmptime) > 0 )
{
/* Passphrase "expired". */
desc = xtryasprintf
(L_("This passphrase has not been changed%%0A"
"since %.4s-%.2s-%.2s. Please change it now."),
protected_at, protected_at+4, protected_at+6);
if (!desc)
return gpg_error_from_syserror ();
}
}
if (desc)
{
/* Change required. */
if (opt.enforce_passphrase_constraints)
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"), NULL, 0);
if (!err)
arg->change_required = 1;
}
else
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"),
L_("I'll change it later"), 0);
if (!err)
arg->change_required = 1;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
err = 0;
}
xfree (desc);
}
return err;
}
/* Return true if the STRING has an %C or %c expando. */
static int
has_comment_expando (const char *string)
{
const char *s;
int percent = 0;
if (!string)
return 0;
for (s = string; *s; s++)
{
if (percent)
{
if (*s == 'c' || *s == 'C')
return 1;
percent = 0;
}
else if (*s == '%')
percent = 1;
}
return 0;
}
/* Modify a Key description, replacing certain special format
characters. List of currently supported replacements:
%% - Replaced by a single %
%c - Replaced by the content of COMMENT.
%C - Same as %c but put into parentheses.
%F - Replaced by an ssh style fingerprint computed from KEY.
The functions returns 0 on success or an error code. On success a
newly allocated string is stored at the address of RESULT.
*/
gpg_error_t
agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result)
{
size_t comment_length;
size_t in_len;
size_t out_len;
char *out;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
char *p;
*result = NULL;
if (!comment)
comment = "";
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
/* FIXME: This can be simplified by using es_fopenmem. */
out = NULL;
out_len = 0;
for (pass=0; pass < 2; pass++)
{
special = 0;
for (i = 0; i < in_len; i++)
{
if (special)
{
special = 0;
switch (in[i])
{
case '%':
if (out)
*out++ = '%';
else
out_len++;
break;
case 'c': /* Comment. */
if (out)
{
memcpy (out, comment, comment_length);
out += comment_length;
}
else
out_len += comment_length;
break;
case 'C': /* Comment. */
if (!comment_length)
;
else if (out)
{
*out++ = '(';
memcpy (out, comment, comment_length);
out += comment_length;
*out++ = ')';
}
else
out_len += comment_length + 2;
break;
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
&ssh_fpr);
if (ssh_fpr)
{
if (out)
out = stpcpy (out, ssh_fpr);
else
out_len += strlen (ssh_fpr);
}
break;
default: /* Invalid special sequences are kept as they are. */
if (out)
{
*out++ = '%';
*out++ = in[i];
}
else
out_len+=2;
break;
}
}
else if (in[i] == '%')
special = 1;
else
{
if (out)
*out++ = in[i];
else
out_len++;
}
}
if (!pass)
{
*result = out = xtrymalloc (out_len + 1);
if (!out)
{
xfree (ssh_fpr);
return gpg_error_from_syserror ();
}
}
}
*out = 0;
log_assert (*result + out_len == out);
xfree (ssh_fpr);
/* The ssh prompt may sometimes end in
* "...%0A ()"
* The empty parentheses doesn't look very good. We use this hack
* here to remove them as well as the indentation spaces. */
p = *result;
i = strlen (p);
if (i > 2 && !strcmp (p + i - 2, "()"))
{
p += i - 2;
*p-- = 0;
while (p > *result && spacep (p))
*p-- = 0;
}
return 0;
}
/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
should be the hex encoded keygrip of that key to be used with the
caching mechanism. DESC_TEXT may be set to override the default
description used for the pinentry. If LOOKUP_TTL is given this
function is used to lookup the default ttl. If R_PASSPHRASE is not
NULL, the function succeeded and the key was protected the used
passphrase (entered or from the cache) is stored there; if not NULL
will be stored. The caller needs to free the returned
passphrase. */
static gpg_error_t
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
unsigned char **keybuf, const unsigned char *grip,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
char **r_passphrase)
{
struct pin_entry_info_s *pi;
struct try_unprotect_arg_s arg;
int rc;
unsigned char *result;
size_t resultlen;
char hexgrip[40+1];
if (r_passphrase)
*r_passphrase = NULL;
bin2hex (grip, 20, hexgrip);
/* Initially try to get it using a cache nonce. */
if (cache_nonce)
{
char *pw;
pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* First try to get it from the cache - if there is none or we can't
unprotect it, we fall back to ask the user */
if (cache_mode != CACHE_MODE_IGNORE)
{
char *pw;
retry:
pw = agent_get_cache (ctrl, hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (cache_mode == CACHE_MODE_NORMAL)
agent_store_cache_hit (hexgrip);
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
else if (cache_mode == CACHE_MODE_NORMAL)
{
/* The standard use of GPG keys is to have a signing and an
encryption subkey. Commonly both use the same
passphrase. We try to help the user to enter the
passphrase only once by silently trying the last
correctly entered passphrase. Checking one additional
passphrase should be acceptable; despite the S2K
introduced delays. The assumed workflow is:
1. Read encrypted message in a MUA and thus enter a
passphrase for the encryption subkey.
2. Reply to that mail with an encrypted and signed
mail, thus entering the passphrase for the signing
subkey.
We can often avoid the passphrase entry in the second
step. We do this only in normal mode, so not to
interfere with unrelated cache entries. */
pw = agent_get_cache (ctrl, NULL, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
&result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* If the pinentry is currently in use, we wait up to 60 seconds
for it to close and check the cache again. This solves a common
situation where several requests for unprotecting a key have
been made but the user is still entering the passphrase for
the first request. Because all requests to agent_askpin are
serialized they would then pop up one after the other to
request the passphrase - despite that the user has already
entered it and is then available in the cache. This
implementation is not race free but in the worst case the
user has to enter the passphrase only once more. */
if (pinentry_active_p (ctrl, 0))
{
/* Active - wait */
if (!pinentry_active_p (ctrl, 60))
{
/* We need to give the other thread a chance to actually put
it into the cache. */
npth_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
}
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_unprotect_cb;
arg.ctrl = ctrl;
arg.protected_key = *keybuf;
arg.unprotected_key = NULL;
arg.change_required = 0;
pi->check_cb_arg = &arg;
rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
if (rc)
{
if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
log_error ("Clearing pinentry cache which caused error %s\n",
gpg_strerror (rc));
agent_clear_passphrase (ctrl, hexgrip, cache_mode);
}
}
else
{
log_assert (arg.unprotected_key);
if (arg.change_required)
{
/* The callback told as that the user should change their
passphrase. Present the dialog to do. */
size_t canlen, erroff;
gcry_sexp_t s_skey;
log_assert (arg.unprotected_key);
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff,
(char*)arg.unprotected_key, canlen);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
rc = agent_protect_and_store (ctrl, s_skey, NULL);
gcry_sexp_release (s_skey);
if (rc)
{
log_error ("changing the passphrase failed: %s\n",
gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
}
else
{
/* Passphrase is fine. */
agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
lookup_ttl? lookup_ttl (hexgrip) : 0);
agent_store_cache_hit (hexgrip);
if (r_passphrase && *pi->pin)
*r_passphrase = xtrystrdup (pi->pin);
}
xfree (*keybuf);
*keybuf = arg.unprotected_key;
}
xfree (pi);
return rc;
}
/* Read the key identified by GRIP from the private key directory and
* return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
* is not NULl and the extended key format is used, the meta data
* items are stored there. However the "Key:" item is removed from
* it. On failure returns an error code and stores NULL at RESULT and
* R_KEYMETA. */
static gpg_error_t
read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
char *fname;
estream_t fp;
struct stat st;
unsigned char *buf;
size_t buflen, erroff;
gcry_sexp_t s_skey;
char hexgrip[40+4+1];
char first;
*result = NULL;
if (r_keymeta)
*r_keymeta = NULL;
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
return err;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
if (es_fseek (fp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
if (first != '(')
{
/* Key is in extended format. */
nvc_t pk = NULL;
int line;
err = nvc_parse_private_key (&pk, &line, fp);
es_fclose (fp);
if (err)
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
else
{
err = nvc_get_private_key (pk, result);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
else
nvc_delete_named (pk, "Key:");
}
if (!err && r_keymeta)
*r_keymeta = pk;
else
nvc_release (pk);
xfree (fname);
return err;
}
if (fstat (es_fileno (fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
return err;
}
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
if (!buf)
{
err = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
return err;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
buflen, fname, gpg_strerror (err));
xfree (fname);
es_fclose (fp);
xfree (buf);
return err;
}
/* Convert the file into a gcrypt S-expression object. */
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
xfree (fname);
es_fclose (fp);
xfree (buf);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
return err;
}
*result = s_skey;
return 0;
}
/* Remove the key identified by GRIP from the private key directory. */
static gpg_error_t
remove_key_file (const unsigned char *grip)
{
gpg_error_t err = 0;
char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
if (gnupg_remove (fname))
err = gpg_error_from_syserror ();
xfree (fname);
return err;
}
/* Return the secret key as an S-Exp in RESULT after locating it using
the GRIP. If the operation shall be diverted to a token, an
allocated S-expression with the shadow_info part from the file is
stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
set to present a custom description for the pinentry. LOOKUP_TTL
is an optional function to convey a TTL to the cache manager; we do
not simply pass the TTL value because the value is only needed if
an unprotect action was needed and looking up the TTL may have some
overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is
given that cache item is first tried to get a passphrase. If
R_PASSPHRASE is not NULL, the function succeeded and the key was
protected the used passphrase (entered or from the cache) is stored
there; if not NULL will be stored. The caller needs to free the
returned passphrase. */
gpg_error_t
agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
const unsigned char *grip, unsigned char **shadow_info,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
gcry_sexp_t *result, char **r_passphrase)
{
gpg_error_t err;
unsigned char *buf;
size_t len, buflen, erroff;
gcry_sexp_t s_skey;
nvc_t keymeta = NULL;
char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
err = read_key_file (grip, &s_skey, &keymeta);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
return err;
}
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
{
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
switch (agent_private_key_type (buf))
{
case PRIVATE_KEY_CLEAR:
break; /* no unprotection needed */
case PRIVATE_KEY_OPENPGP_NONE:
{
unsigned char *buf_new;
size_t buf_newlen;
err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
if (err)
log_error ("failed to convert unprotected openpgp key: %s\n",
gpg_strerror (err));
else
{
xfree (buf);
buf = buf_new;
}
}
break;
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
char *comment_buffer = NULL;
const char *comment = NULL;
/* Note, that we will take the comment as a C string for
* display purposes; i.e. all stuff beyond a Nul character is
* ignored. If a "Label" entry is available in the meta data
* this is used instead of the s-ecpression comment. */
if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
{
if (strchr (comment, '\n')
&& (comment_buffer = linefeed_to_percent0A (comment)))
comment = comment_buffer;
/* In case DESC_TEXT has no escape pattern for a comment
* we append one. */
if (desc_text && !has_comment_expando (desc_text))
{
desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
if (desc_text_buffer)
desc_text = desc_text_buffer;
}
}
else
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
comment = comment_buffer;
}
desc_text_final = NULL;
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
gcry_free (comment_buffer);
if (!err)
{
err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
cache_mode, lookup_ttl, r_passphrase);
if (err)
log_error ("failed to unprotect the secret key: %s\n",
gpg_strerror (err));
}
xfree (desc_text_final);
}
break;
case PRIVATE_KEY_SHADOWED:
if (shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info (buf, &s);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
err = out_of_core ();
else
{
memcpy (*shadow_info, s, n);
err = 0;
}
}
if (err)
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
}
else
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
if (err)
{
xfree (buf);
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
wipememory (buf, buflen);
xfree (buf);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
*result = s_skey;
nvc_release (keymeta);
xfree (desc_text_buffer);
return 0;
}
/* Return the string name from the S-expression S_KEY as well as a
string describing the names of the parameters. ALGONAMESIZE and
ELEMSSIZE give the allocated size of the provided buffers. The
buffers may be NULL if not required. If R_LIST is not NULL the top
level list will be stored there; the caller needs to release it in
this case. */
static gpg_error_t
key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
char *r_algoname, size_t algonamesize,
char *r_elems, size_t elemssize)
{
gcry_sexp_t list, l2;
const char *name, *algoname, *elems;
size_t n;
if (r_list)
*r_list = NULL;
list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "private-key", 0 );
if (!list)
list = gcry_sexp_find_token (s_key, "public-key", 0 );
if (!list)
{
log_error ("invalid private key format\n");
return gpg_error (GPG_ERR_BAD_SECKEY);
}
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if (n==3 && !memcmp (name, "rsa", 3))
{
algoname = "rsa";
elems = "ne";
}
else if (n==3 && !memcmp (name, "dsa", 3))
{
algoname = "dsa";
elems = "pqgy";
}
else if (n==3 && !memcmp (name, "ecc", 3))
{
algoname = "ecc";
elems = "pabgnq";
}
else if (n==5 && !memcmp (name, "ecdsa", 5))
{
algoname = "ecdsa";
elems = "pabgnq";
}
else if (n==4 && !memcmp (name, "ecdh", 4))
{
algoname = "ecdh";
elems = "pabgnq";
}
else if (n==3 && !memcmp (name, "elg", 3))
{
algoname = "elg";
elems = "pgy";
}
else
{
log_error ("unknown private key algorithm\n");
gcry_sexp_release (list);
return gpg_error (GPG_ERR_BAD_SECKEY);
}
if (r_algoname)
{
if (strlen (algoname) >= algonamesize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_algoname, algoname);
}
if (r_elems)
{
if (strlen (elems) >= elemssize)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
strcpy (r_elems, elems);
}
if (r_list)
*r_list = list;
else
gcry_sexp_release (list);
return 0;
}
/* Return true if KEYPARMS holds an EdDSA key. */
static int
is_eddsa (gcry_sexp_t keyparms)
{
int result = 0;
gcry_sexp_t list;
const char *s;
size_t n;
int i;
list = gcry_sexp_find_token (keyparms, "flags", 0);
for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (list, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
result = 1;
break;
}
}
gcry_sexp_release (list);
return result;
}
/* Return the public key algorithm number if S_KEY is a DSA style key.
If it is not a DSA style key, return 0. */
int
agent_is_dsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an DSA key. */
if (!strcmp (algoname, "dsa"))
result = GCRY_PK_DSA;
else if (!strcmp (algoname, "ecc"))
{
if (is_eddsa (list))
result = 0;
else
result = GCRY_PK_ECDSA;
}
else if (!strcmp (algoname, "ecdsa"))
result = GCRY_PK_ECDSA;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */
int
agent_is_eddsa_key (gcry_sexp_t s_key)
{
int result;
gcry_sexp_t list;
char algoname[6];
if (!s_key)
return 0;
if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
return 0; /* Error - assume it is not an EdDSA key. */
if (!strcmp (algoname, "ecc") && is_eddsa (list))
result = 1;
else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */
result = 1;
else
result = 0;
gcry_sexp_release (list);
return result;
}
/* Return the key for the keygrip GRIP. The result is stored at
RESULT. This function extracts the key from the private key
database and returns it as an S-expression object as it is. On
failure an error code is returned and NULL stored at RESULT. */
gpg_error_t
agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
err = read_key_file (grip, &s_skey, NULL);
if (!err)
*result = s_skey;
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. */
gpg_error_t
agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
gcry_sexp_t uri_sexp, comment_sexp;
const char *uri, *comment;
size_t uri_length, comment_length;
int uri_intlen, comment_intlen;
char *format, *p;
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
for comment + end-of-list. */
int argidx;
gcry_sexp_t list = NULL;
const char *s;
(void)ctrl;
*result = NULL;
err = read_key_file (grip, &s_skey, NULL);
if (err)
return err;
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
array, DIM (array), &curve, &flags);
if (err)
{
gcry_sexp_release (s_skey);
return err;
}
uri = NULL;
uri_length = 0;
uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
if (uri_sexp)
uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
comment = NULL;
comment_length = 0;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
gcry_sexp_release (s_skey);
s_skey = NULL;
/* FIXME: The following thing is pretty ugly code; we should
investigate how to make it cleaner. Probably code to handle
canonical S-expressions in a memory buffer is better suited for
such a task. After all that is what we do in protect.c. Need
to find common patterns and write a straightformward API to use
them. */
log_assert (sizeof (size_t) <= sizeof (void*));
format = xtrymalloc (15+4+7*npkey+10+15+1+1);
if (!format)
{
err = gpg_error_from_syserror ();
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
return err;
}
argidx = 0;
p = stpcpy (stpcpy (format, "(public-key("), algoname);
p = stpcpy (p, "%S%S"); /* curve name and flags. */
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
*p++ = '(';
*p++ = *s++;
p = stpcpy (p, " %m)");
log_assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
*p++ = ')';
if (uri)
{
p = stpcpy (p, "(uri %b)");
log_assert (argidx+1 < DIM (args));
uri_intlen = (int)uri_length;
args[argidx++] = (void *)&uri_intlen;
args[argidx++] = (void *)&uri;
}
if (comment)
{
p = stpcpy (p, "(comment %b)");
log_assert (argidx+1 < DIM (args));
comment_intlen = (int)comment_length;
args[argidx++] = (void *)&comment_intlen;
args[argidx++] = (void*)&comment;
}
*p++ = ')';
*p = 0;
log_assert (argidx < DIM (args));
args[argidx] = NULL;
err = gcry_sexp_build_array (&list, NULL, format, args);
xfree (format);
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
if (!err)
*result = list;
return err;
}
/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (const unsigned char *grip)
{
int result;
char *fname;
char hexgrip[40+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
result = !access (fname, R_OK)? 0 : -1;
xfree (fname);
return result;
}
/* Return the information about the secret key specified by the binary
keygrip GRIP. If the key is a shadowed one the shadow information
will be stored at the address R_SHADOW_INFO as an allocated
S-expression. */
gpg_error_t
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype, unsigned char **r_shadow_info)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
int keytype;
(void)ctrl;
if (r_keytype)
*r_keytype = PRIVATE_KEY_UNKNOWN;
if (r_shadow_info)
*r_shadow_info = NULL;
{
gcry_sexp_t sexp;
err = read_key_file (grip, &sexp, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
return gpg_error (GPG_ERR_NOT_FOUND);
else
return err;
}
err = make_canon_sexp (sexp, &buf, &len);
gcry_sexp_release (sexp);
if (err)
return err;
}
keytype = agent_private_key_type (buf);
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
break;
case PRIVATE_KEY_PROTECTED:
/* If we ever require it we could retrieve the comment fields
from such a key. */
break;
case PRIVATE_KEY_SHADOWED:
if (r_shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info (buf, &s);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
log_assert (n);
*r_shadow_info = xtrymalloc (n);
if (!*r_shadow_info)
err = gpg_error_from_syserror ();
else
memcpy (*r_shadow_info, s, n);
}
}
break;
default:
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
if (!err && r_keytype)
*r_keytype = keytype;
xfree (buf);
return err;
}
/* Delete the key with GRIP from the disk after having asked for
* confirmation using DESC_TEXT. If FORCE is set the function won't
* require a confirmation via Pinentry or warns if the key is also
* used by ssh. If ONLY_STUBS is set only stub keys (references to
* smartcards) will be affected.
*
* Common error codes are:
* GPG_ERR_NO_SECKEY
* GPG_ERR_KEY_ON_CARD
* GPG_ERR_NOT_CONFIRMED
* GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
*/
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force, int only_stubs)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
unsigned char *buf = NULL;
size_t len;
char *desc_text_final = NULL;
char *comment = NULL;
ssh_control_file_t cf = NULL;
char hexgrip[40+4+1];
char *default_desc = NULL;
int key_type;
err = read_key_file (grip, &s_skey, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
goto leave;
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
goto leave;
key_type = agent_private_key_type (buf);
if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
switch (key_type)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
case PRIVATE_KEY_PROTECTED:
bin2hex (grip, 20, hexgrip);
if (!force)
{
if (!desc_text)
{
default_desc = xtryasprintf
(L_("Do you really want to delete the key identified by keygrip%%0A"
" %s%%0A %%C%%0A?"), hexgrip);
desc_text = default_desc;
}
/* Note, that we will take the comment as a C string for
display purposes; i.e. all stuff beyond a Nul character is
ignored. */
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
}
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
if (err)
goto leave;
err = agent_get_confirmation (ctrl, desc_text_final,
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
cf = ssh_open_control_file ();
if (cf)
{
if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
{
err = agent_get_confirmation
(ctrl,
L_("Warning: This key is also listed for use with SSH!\n"
"Deleting the key might remove your ability to "
"access remote machines."),
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
}
}
}
err = remove_key_file (grip);
break;
case PRIVATE_KEY_SHADOWED:
err = remove_key_file (grip);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
leave:
ssh_close_control_file (cf);
gcry_free (comment);
xfree (desc_text_final);
xfree (default_desc);
xfree (buf);
gcry_sexp_release (s_skey);
return err;
}
/* Write an S-expression formatted shadow key to our key storage.
Shadow key is created by an S-expression public key in PKBUF and
card's SERIALNO and the IDSTRING. With FORCE passed as true an
existing key with the given GRIP will get overwritten. */
gpg_error_t
agent_write_shadow_key (const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force)
{
gpg_error_t err;
unsigned char *shadow_info;
unsigned char *shdkey;
size_t len;
/* Just in case some caller did not parse the stuff correctly, skip
* leading spaces. */
while (spacep (serialno))
serialno++;
while (spacep (keyid))
keyid++;
shadow_info = make_shadow_info (serialno, keyid);
if (!shadow_info)
return gpg_error_from_syserror ();
err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
xfree (shadow_info);
if (err)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
return err;
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid);
xfree (shdkey);
if (err)
log_error ("error writing key: %s\n", gpg_strerror (err));
return err;
}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index 70e9e229f..4678cf359 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,3276 +1,3276 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2000-2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <aclapi.h>
# include <sddl.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "agent.h"
#include <assuan.h> /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/asshelp.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
aUseStandardSocketP,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugQuickRandom,
oDebugPinentry,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oGrab,
oNoGrab,
oLogFile,
oServer,
oDaemon,
oSupervised,
oBatch,
oPinentryProgram,
oPinentryTouchFile,
oPinentryInvisibleChar,
oPinentryTimeout,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oScdaemonProgram,
oDefCacheTTL,
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oMaxPassphraseDays,
oEnablePassphraseHistory,
oDisableExtendedKeyFormat,
oEnableExtendedKeyFormat,
oUseStandardSocket,
oNoUseStandardSocket,
oExtraSocket,
oBrowserSocket,
oFakedSystemTime,
oIgnoreCacheForSigning,
oAllowMarkTrusted,
oNoAllowMarkTrusted,
oAllowPresetPassphrase,
oAllowLoopbackPinentry,
oNoAllowLoopbackPinentry,
oNoAllowExternalCache,
oAllowEmacsPinentry,
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
oSSHFingerprintDigest,
oPuttySupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oS2KCount,
oS2KCalibration,
oAutoExpandSecmem,
oListenBacklog,
oWriteEnvFile,
oNoop
};
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")),
#endif
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oGrab, "grab", "@"),
/* FIXME: Add the below string for 2.3 */
/* N_("let PIN-Entry grab keyboard and mouse")), */
ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
/* */ N_("|PGM|use PGM as the PIN-Entry program")),
ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"),
ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
/* */ N_("|PGM|use PGM as the SCdaemon program") ),
ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
/* */ N_("do not use the SCdaemon") ),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oExtraSocket, "extra-socket",
/* */ N_("|NAME|accept some commands via NAME")),
ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_n (oKeepTTY, "keep-tty",
/* */ N_("ignore requests to change the TTY")),
ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
/* */ N_("ignore requests to change the X display")),
ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
N_("|N|expire cached PINs after N seconds")),
ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ),
ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ),
ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ),
ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
/* */ "@"),
ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"),
ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"),
ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"),
ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"),
ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"),
ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
/* */ N_("do not use the PIN cache when signing")),
ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
/* */ N_("disallow the use of an external password cache")),
ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
/* */ N_("disallow clients to mark keys as \"trusted\"")),
ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
/* */ N_("allow presetting passphrase")),
ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
N_("disallow caller to override the pinentry")),
ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
/* */ N_("allow passphrase to be prompted through Emacs")),
ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
N_("|ALGO|use ALGO to show ssh fingerprints")),
ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable putty support")
#else
/* */ "@"
#endif
),
ARGPARSE_s_n (oDisableExtendedKeyFormat, "disable-extended-key-format", "@"),
ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"),
ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"),
ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
/* Dummy options for backward compatibility. */
ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
/* Dummy options. */
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
#define MAX_PASSPHRASE_DAYS (0)
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms except for
* Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
#else
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
#endif
/* Flag indicating that the ssh-agent subsystem has been enabled. */
static int ssh_support;
#ifdef HAVE_W32_SYSTEM
/* Flag indicating that support for Putty has been enabled. */
static int putty_support;
/* A magic value used with WM_COPYDATA. */
#define PUTTY_IPC_MAGIC 0x804e50ba
/* To avoid surprises we limit the size of the mapped IPC file to this
value. Putty currently (0.62) uses 8k, thus 16k should be enough
for the foreseeable future. */
#define PUTTY_IPC_MAXLEN 16384
#endif /*HAVE_W32_SYSTEM*/
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flags to indicate that check_own_socket shall not be called. */
static int disable_check_own_socket;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Name of the communication socket used for native gpg-agent
requests. The second variable is either NULL or a malloced string
with the real socket name in case it has been redirected. */
static char *socket_name;
static char *redir_socket_name;
/* Name of the optional extra socket used for native gpg-agent requests. */
static char *socket_name_extra;
static char *redir_socket_name_extra;
/* Name of the optional browser socket used for native gpg-agent requests. */
static char *socket_name_browser;
static char *redir_socket_name_browser;
/* Name of the communication socket used for ssh-agent protocol. */
static char *socket_name_ssh;
static char *redir_socket_name_ssh;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
static assuan_sock_nonce_t socket_nonce_extra;
static assuan_sock_nonce_t socket_nonce_browser;
static assuan_sock_nonce_t socket_nonce_ssh;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Default values for options passed to the pinentry. */
static char *default_display;
static char *default_ttyname;
static char *default_ttytype;
static char *default_lc_ctype;
static char *default_lc_messages;
static char *default_xauthority;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Helper to implement --debug-level */
static const char *debug_level;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* The handle_tick() function may test whether a parent is still
* running. We record the PID of the parent here or -1 if it should
* be watched. */
static pid_t parent_pid = (pid_t)(-1);
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how gpg-agent was started, the homedir inotify watch
* may not be reliable. This flag is set if we assume that inotify
* works reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void agent_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void agent_init_default_ctrl (ctrl_t ctrl);
static void agent_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh);
static void check_own_socket (void);
static int check_for_running_agent (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
This function is expected to be called only once. GETFNC is
expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
/* Return strings describing this program. The case values are
described in common/argparse.c:strusage. The values here override
the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@GPG_AGENT@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
only the active debug flags are propagated to the subsystems. With
DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
all flags already set. Note that we don't fail here, because it is
important to keep gpg-agent running even after re-reading the
options due to a SIGHUP. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
the corresponding real name if the socket has been redirected. */
static void
remove_socket (char *name, char *redir_name)
{
if (name && *name)
{
if (redir_name)
name = redir_name;
gnupg_remove (name);
*name = 0;
}
}
/* Discover which inherited file descriptors correspond to which
* services/sockets offered by gpg-agent, using the LISTEN_FDS and
* LISTEN_FDNAMES convention. The understood labels are "ssh",
* "extra", and "browser". "std" or other labels will be interpreted
* as the standard socket.
*
* This function is designed to log errors when the expected file
* descriptors don't make sense, but to do its best to continue to
* work even in the face of minor misconfigurations.
*
* For more information on the LISTEN_FDS convention, see
* sd_listen_fds(3) on certain Linux distributions.
*/
#ifndef HAVE_W32_SYSTEM
static void
map_supervised_sockets (gnupg_fd_t *r_fd,
gnupg_fd_t *r_fd_extra,
gnupg_fd_t *r_fd_browser,
gnupg_fd_t *r_fd_ssh)
{
struct {
const char *label;
int **fdaddr;
char **nameaddr;
} tbl[] = {
{ "ssh", &r_fd_ssh, &socket_name_ssh },
{ "browser", &r_fd_browser, &socket_name_browser },
{ "extra", &r_fd_extra, &socket_name_extra },
{ "std", &r_fd, &socket_name } /* (Must be the last item.) */
};
const char *envvar;
char **fdnames;
int nfdnames;
int fd_count;
*r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
/* Print a warning if LISTEN_PID does not match outr pid. */
envvar = getenv ("LISTEN_PID");
if (!envvar)
log_error ("no LISTEN_PID environment variable found in "
"--supervised mode (ignoring)\n");
else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
log_error ("environment variable LISTEN_PID (%lu) does not match"
" our pid (%lu) in --supervised mode (ignoring)\n",
(unsigned long)strtoul (envvar, NULL, 10),
(unsigned long)getpid ());
/* Parse LISTEN_FDNAMES into the array FDNAMES. */
envvar = getenv ("LISTEN_FDNAMES");
if (envvar)
{
fdnames = strtokenize (envvar, ":");
if (!fdnames)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
agent_exit (1);
}
for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
;
}
else
{
fdnames = NULL;
nfdnames = 0;
}
/* Parse LISTEN_FDS into fd_count or provide a replacement. */
envvar = getenv ("LISTEN_FDS");
if (envvar)
fd_count = atoi (envvar);
else if (fdnames)
{
log_error ("no LISTEN_FDS environment variable found in --supervised"
" mode (relying on LISTEN_FDNAMES instead)\n");
fd_count = nfdnames;
}
else
{
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
"found in --supervised mode"
" (assuming 1 active descriptor)\n");
fd_count = 1;
}
if (fd_count < 1)
{
log_error ("--supervised mode expects at least one file descriptor"
" (was told %d, carrying on as though it were 1)\n",
fd_count);
fd_count = 1;
}
/* Assign the descriptors to the return values. */
if (!fdnames)
{
struct stat statbuf;
if (fd_count != 1)
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
" in --supervised mode."
" (ignoring all sockets but the first one)\n",
fd_count);
if (fstat (3, &statbuf) == -1 && errno ==EBADF)
log_fatal ("file descriptor 3 must be valid in --supervised mode"
" if LISTEN_FDNAMES is not set\n");
*r_fd = 3;
socket_name = gnupg_get_socket_name (3);
}
else if (fd_count != nfdnames)
{
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
"LISTEN_FDS (%d) in --supervised mode\n",
nfdnames, fd_count);
}
else
{
int i, j, fd;
char *name;
for (i = 0; i < nfdnames; i++)
{
for (j = 0; j < DIM (tbl); j++)
{
if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
{
fd = 3 + i;
if (**tbl[j].fdaddr == -1)
{
name = gnupg_get_socket_name (fd);
if (name)
{
**tbl[j].fdaddr = fd;
*tbl[j].nameaddr = name;
log_info ("using fd %d for %s socket (%s)\n",
fd, tbl[j].label, name);
}
else
{
log_error ("cannot listen on fd %d for %s socket\n",
fd, tbl[j].label);
close (fd);
}
}
else
{
log_error ("cannot listen on more than one %s socket\n",
tbl[j].label);
close (fd);
}
break;
}
}
}
}
xfree (fdnames);
}
#endif /*!HAVE_W32_SYSTEM*/
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
deinitialize_module_cache ();
if (!is_supervised && !inhibit_socket_removal)
{
remove_socket (socket_name, redir_socket_name);
if (opt.extra_socket > 1)
remove_socket (socket_name_extra, redir_socket_name_extra);
if (opt.browser_socket > 1)
remove_socket (socket_name_browser, redir_socket_name_browser);
remove_socket (socket_name_ssh, redir_socket_name_ssh);
}
}
/* Handle options which are allowed to be reset after program start.
Return true when the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
int i;
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.no_grab = 1;
opt.debug_pinentry = 0;
opt.pinentry_program = NULL;
opt.pinentry_touch_file = NULL;
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = NULL;
opt.pinentry_timeout = 0;
opt.scdaemon_program = NULL;
opt.def_cache_ttl = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
opt.enable_passphrase_history = 0;
opt.enable_extended_key_format = 1;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 1;
opt.allow_external_cache = 1;
opt.allow_loopback_pinentry = 1;
opt.allow_emacs_pinentry = 0;
opt.disable_scdaemon = 0;
disable_check_own_socket = 0;
/* Note: When changing the next line, change also gpgconf_list. */
opt.ssh_fingerprint_digest = GCRY_MD_MD5;
opt.s2k_count = 0;
set_s2k_calibration_time (0); /* Set to default. */
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oDebugPinentry: opt.debug_pinentry = 1; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oNoGrab: opt.no_grab |= 1; break;
case oGrab: opt.no_grab |= 2; break;
case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
case oPinentryInvisibleChar:
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
break;
case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
case oDisableScdaemon: opt.disable_scdaemon = 1; break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oMaxPassphraseDays:
opt.max_passphrase_days = pargs->r.ret_ulong;
break;
case oEnablePassphraseHistory:
opt.enable_passphrase_history = 1;
break;
case oEnableExtendedKeyFormat:
opt.enable_extended_key_format = 2;
break;
case oDisableExtendedKeyFormat:
if (opt.enable_extended_key_format != 2)
opt.enable_extended_key_format = 0;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
case oNoAllowExternalCache: opt.allow_external_cache = 0;
break;
case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
break;
case oSSHFingerprintDigest:
i = gcry_md_map_name (pargs->r.ret_str);
if (!i)
log_error (_("selected digest algorithm is invalid\n"));
else
opt.ssh_fingerprint_digest = i;
break;
case oS2KCount:
opt.s2k_count = pargs->r.ret_ulong;
break;
case oS2KCalibration:
set_s2k_calibration_time (pargs->r.ret_ulong);
break;
case oNoop: break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
/* Hack to allow --grab to override --no-grab. */
if ((opt.no_grab & 2))
opt.no_grab = 0;
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
#if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
#endif
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
initialize_module_cache ();
initialize_module_call_pinentry ();
initialize_module_call_scd ();
initialize_module_trustlist ();
}
/* The main entry point. */
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
gpg_error_t err;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
file descriptors and the signal mask. This info is required to
do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
disable_core_dumps ();
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Record some of the original environment strings. */
{
const char *s;
int idx;
static const char *names[] =
{ "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
err = 0;
opt.startup_env = session_env_new ();
if (!opt.startup_env)
err = gpg_error_from_syserror ();
for (idx=0; !err && names[idx]; idx++)
{
s = getenv (names[idx]);
if (s)
err = session_env_setenv (opt.startup_env, names[idx], s);
}
if (!err)
{
s = gnupg_ttyname (0);
if (s)
err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
}
if (err)
log_fatal ("error recording startup environment: %s\n",
gpg_strerror (err));
/* Fixme: Better use the locale function here. */
opt.startup_lc_ctype = getenv ("LC_CTYPE");
if (opt.startup_lc_ctype)
opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
opt.startup_lc_messages = getenv ("LC_MESSAGES");
if (opt.startup_lc_messages)
opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
}
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
else if (pargs.r_opt == oDebugQuickRandom)
{
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
}
}
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
/* Save the default conf file name so that
reread_configuration is able to test whether the
config file has been created in the meantime. */
xfree (config_filename);
config_filename = configname;
configname = NULL;
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case aUseStandardSocketP: gpgconf_list = 3; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oSupervised: is_supervised = 1; break;
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
break;
case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
break;
case oUseStandardSocket:
case oNoUseStandardSocket:
obsolete_option (configname, configlineno, "use-standard-socket");
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oKeepTTY: opt.keep_tty = 1; break;
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport:
ssh_support = 1;
break;
case oPuttySupport:
# ifdef HAVE_W32_SYSTEM
putty_support = 1;
# endif
break;
case oExtraSocket:
opt.extra_socket = 1; /* (1 = points into argv) */
socket_name_extra = pargs.r.ret_str;
break;
case oBrowserSocket:
opt.browser_socket = 1; /* (1 = points into argv) */
socket_name_browser = pargs.r.ret_str;
break;
case oAutoExpandSecmem:
/* Try to enable this option. It will officially only be
* supported by Libgcrypt 1.9 but 1.8.2 already supports it
* on the quiet and thus we use the numeric value value. */
gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
(unsigned int)pargs.r.ret_ulong, 0);
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oDebugQuickRandom:
/* Only used by the first stage command line parser. */
break;
case oWriteEnvFile:
obsolete_option (configname, configlineno, "write-env-file");
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
if (config_filename != configname)
{
xfree (config_filename);
config_filename = configname;
}
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* gpg-agent usually does not output any messages because it runs in
the background. For log files it is acceptable to have messages
always encoded in utf-8. We switch here to utf-8, so that
commands like --help still give native messages. It is far
easier to switch only once instead of for every message and it
actually helps when more then one thread is active (avoids an
extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
{
/* We have been called without any command and thus we merely
check whether an agent is already running. We do this right
here so that we don't clobber a logfile with this check but
print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_agent (0);
agent_exit (0);
}
if (is_supervised)
;
else if (!opt.extra_socket)
opt.extra_socket = 1;
else if (socket_name_extra
&& (!strcmp (socket_name_extra, "none")
|| !strcmp (socket_name_extra, "/dev/null")))
{
/* User requested not to create this socket. */
opt.extra_socket = 0;
socket_name_extra = NULL;
}
if (is_supervised)
;
else if (!opt.browser_socket)
opt.browser_socket = 1;
else if (socket_name_browser
&& (!strcmp (socket_name_browser, "none")
|| !strcmp (socket_name_browser, "/dev/null")))
{
/* User requested not to create this socket. */
opt.browser_socket = 0;
socket_name_browser = NULL;
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 3)
{
/* We now use the standard socket always - return true for
backward compatibility. */
agent_exit (0);
}
else if (gpgconf_list == 2)
agent_exit (0);
else if (gpgconf_list)
{
char *filename;
char *filename_esc;
/* List options and default values in the GPG Conf format. */
filename = make_filename (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, GPG_AGENT_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename);
xfree (filename_esc);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
es_printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL );
es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH );
es_printf ("max-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
es_printf ("enforce-passphrase-constraints:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MIN_PASSPHRASE_NONALPHA);
es_printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("max-passphrase-days:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MAX_PASSPHRASE_DAYS);
es_printf ("enable-passphrase-history:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("ignore-cache-for-signing:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-external-cache:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("no-allow-mark-trusted:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("disable-scdaemon:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, "md5");
#ifdef HAVE_W32_SYSTEM
es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE);
#endif
es_printf ("no-allow-loopback-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("allow-emacs-pinentry:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("pinentry-timeout:%lu:0:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
es_printf ("grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
agent_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
/* Make sure that we have a default ttyname. */
if (!default_ttyname && gnupg_ttyname (1))
default_ttyname = xstrdup (gnupg_ttyname (1));
if (!default_ttytype && getenv ("TERM"))
default_ttytype = xstrdup (getenv ("TERM"));
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
agent_exit (1);
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
xfree (ctrl);
agent_exit (1);
}
agent_init_default_ctrl (ctrl);
start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (is_supervised)
{
#ifndef HAVE_W32_SYSTEM
gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
initialize_modules ();
/* when supervised and sending logs to stderr, the process
supervisor should handle log entry metadata (pid, name,
timestamp) */
if (!logfile)
log_set_prefix (NULL, 0);
log_info ("%s %s starting in supervised mode.\n",
strusage(11), strusage(13) );
/* See below in "regular server mode" on why we remove certain
* envvars. */
if (!opt.keep_display)
gnupg_unsetenv ("DISPLAY");
gnupg_unsetenv ("INSIDE_EMACS");
/* Virtually create the sockets. Note that we use -1 here
* because the whole thing works only on Unix. */
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
if (fd == -1)
log_fatal ("no standard socket provided\n");
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
fd, fd_extra, fd_browser, fd_ssh);
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
#endif /*!HAVE_W32_SYSTEM*/
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular server mode */
gnupg_fd_t fd;
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Remove the DISPLAY variable so that a pinentry does not
default to a specific display. There is still a default
display when gpg-agent was started using --display or a
client requested this using an OPTION command. Note, that we
don't do this when running in reverse daemon mode (i.e. when
exec the program given as arguments). */
#ifndef HAVE_W32_SYSTEM
if (!opt.keep_display && !argc)
gnupg_unsetenv ("DISPLAY");
#endif
/* Remove the INSIDE_EMACS variable so that a pinentry does not
always try to interact with Emacs. The variable is set when
a client requested this using an OPTION command. */
gnupg_unsetenv ("INSIDE_EMACS");
/* Create the sockets. */
socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 1, 0,
&redir_socket_name, &socket_nonce);
if (opt.extra_socket)
{
if (socket_name_extra)
socket_name_extra = create_socket_name (socket_name_extra, 0);
else
socket_name_extra = create_socket_name
/**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
opt.extra_socket = 2; /* Indicate that it has been malloced. */
fd_extra = create_server_socket (socket_name_extra, 0, 0,
&redir_socket_name_extra,
&socket_nonce_extra);
}
if (opt.browser_socket)
{
if (socket_name_browser)
socket_name_browser = create_socket_name (socket_name_browser, 0);
else
socket_name_browser= create_socket_name
/**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
opt.browser_socket = 2; /* Indicate that it has been malloced. */
fd_browser = create_server_socket (socket_name_browser, 0, 0,
&redir_socket_name_browser,
&socket_nonce_browser);
}
socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
&redir_socket_name_ssh,
&socket_nonce_ssh);
/* If we are going to exec a program in the parent, we record
the PID, so that the child may check whether the program is
still alive. */
if (argc)
parent_pid = getpid ();
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
char *infostr_ssh_sock, *infostr_ssh_valid;
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
we restore it. That is not strictly necessary but some
programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
/* Create the SSH info string if enabled. */
if (ssh_support)
{
if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
socket_name_ssh) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
(unsigned long)getpid()) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
}
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
if (opt.extra_socket)
*socket_name_extra = 0;
if (opt.browser_socket)
*socket_name_browser = 0;
*socket_name_ssh = 0;
if (argc)
{ /* Run the program given on the commandline. */
if (ssh_support && (putenv (infostr_ssh_sock)
|| putenv (infostr_ssh_valid)))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
/* Close all the file descriptors except the standard
ones and those open at startup. We explicitly don't
close 0,1,2 in case something went wrong collecting
them at startup. */
close_all_fds (3, startup_fd_list);
/* Run the command. */
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
if (ssh_support)
{
*strchr (infostr_ssh_sock, '=') = ' ';
es_printf ("setenv %s;\n", infostr_ssh_sock);
}
}
else
{
if (ssh_support)
{
es_printf ("%s; export SSH_AUTH_SOCK;\n",
infostr_ssh_sock);
}
}
if (ssh_support)
{
xfree (infostr_ssh_sock);
xfree (infostr_ssh_valid);
}
exit (0);
}
/*NOTREACHED*/
} /* End parent */
/*
This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Unless we are running with a program given on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
if (!argc)
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
log_info ("%s %s started\n", strusage(11), strusage(13) );
handle_connections (fd, fd_extra, fd_browser, fd_ssh);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
agent_exit (int rc)
{
/*FIXME: update_random_seed_file();*/
/* We run our cleanup handler because that may close cipher contexts
stored in secure memory and thus this needs to be done before we
explicitly terminate secure memory. */
cleanup ();
#if 1
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
set once at startup and dispatches progress messages to the
corresponding threads of the agent. */
static void
agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
/* Libgcrypt < 1.8 does not know about nPth and thus when it reads
* from /dev/random this will block the process. To mitigate this
* problem we yield the thread when Libgcrypt tells us that it needs
* more entropy. This way other threads have chance to run. */
#if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */
if (what && !strcmp (what, "need_entropy"))
{
#if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */
/* In older gpg-error versions gpgrt_yield is buggy for use with
* nPth and thus we need to resort to a sleep call. */
npth_usleep (1000); /* 1ms */
#else
gpgrt_yield ();
#endif
}
#endif
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
structure usually identified by an argument named CTRL. This
function is called immediately after allocating the control
structure. Its purpose is to setup the default values for that
structure. Note that some values may have already been set. */
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
log_assert (ctrl->session_env);
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to agent_init_default_ctrl. */
static void
agent_deinit_default_ctrl (ctrl_t ctrl)
{
unregister_progress_cb ();
session_env_release (ctrl->session_env);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
}
/* Because the ssh protocol does not send us information about the
current TTY setting, we use this function to use those from startup
or those explicitly set. This is also used for the restricted mode
where we ignore requests to change the environment. */
gpg_error_t
agent_copy_startup_env (ctrl_t ctrl)
{
gpg_error_t err = 0;
int iterator = 0;
const char *name, *value;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
if ((value = session_env_getenv (opt.startup_env, name)))
err = session_env_setenv (ctrl->session_env, name, value);
}
if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
err = gpg_error_from_syserror ();
if (err)
log_error ("error setting default session environment: %s\n",
gpg_strerror (err));
return err;
}
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the PTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
- allocated or points into the process' original arguments. Unless
+ allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!config_filename)
return; /* No config file. */
fp = fopen (config_filename, "r");
if (!fp)
{
log_info (_("option file '%s': %s\n"),
config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
finalize_rereadable_options ();
set_debug ();
}
/* Return the file name of the socket we are using for native
requests. */
const char *
get_agent_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the file name of the socket we are using for SSH
requests. */
const char *
get_agent_ssh_socket_name (void)
{
const char *s = socket_name_ssh;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_agent_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
void *
get_agent_scd_notify_event (void)
{
static HANDLE the_event = INVALID_HANDLE_VALUE;
if (the_event == INVALID_HANDLE_VALUE)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd notify event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd notify event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
the_event = h2;
}
}
return the_event;
}
#endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/
/* Create a name for the socket in the home directory as using
STANDARD_NAME. We also check for valid characters as well as
against a maximum allowed length for a unix domain socket is done.
The function terminates the process in case of an error. Returns:
Pointer to an allocated string with the absolute name of the socket
used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
agent_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. Note that this
function needs to be used for the regular socket first (indicated
by PRIMARY) and only then for the extra and the ssh sockets. If
the socket has been redirected the name of the real socket is
stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
agent_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* Our error code mapping on W32CE returns EEXIST thus we also test
for this. */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a gpg-agent is already running. We do this
test only if this is the primary socket. For secondary
sockets we assume that a test for gpg-agent has already been
done and reuse the requested socket. Testing the ssh-socket
is not possible because at this point, though we know the new
Assuan socket, the Assuan server and thus the ssh-agent
server is not yet operational; this would lead to a hang. */
if (primary && !check_for_running_agent (1))
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a gpg-agent is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the private keys exists and
create it if not. This function won't fail as it is only a
convenience function and not strictly necessary. */
static void
create_private_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we compare only the suffix if we see that the default
homedir does start with a tilde. We don't stop here in case of
problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if ( stat (home, &statbuf) )
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_private_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_private_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* If we are running as a child of another process, check whether
the parent is still alive and shutdown if not. */
#ifndef HAVE_W32_SYSTEM
if (parent_pid != (pid_t)(-1))
{
if (kill (parent_pid, 0))
{
shutdown_pending = 2;
log_info ("parent process died - shutting down\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Need to check for expired cache entries. */
agent_cache_housekeeping ();
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
other places too. This is only used when build for W32. */
void
agent_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
agent_flush_cache (0);
reread_configuration ();
agent_reload_trustlist ();
/* We flush the module name cache so that after installing a
"pinentry" binary that one can be used in case the
"pinentry-basic" fallback was in use. */
gnupg_module_name_flush_some ();
if (opt.disable_scdaemon)
agent_card_killscd ();
}
/* A helper function to handle SIGUSR2. */
static void
agent_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - updating card event counter\n");
/* Nothing to check right now. We only increment a counter. */
bump_card_eventcounter ();
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
agent_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
agent_query_dump_state ();
agent_scd_dump_state ();
break;
case SIGUSR2:
agent_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
agent_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
agent_exit (0);
break;
#endif
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
#ifdef HAVE_W32_SYSTEM
/* The window message processing function for Putty. Warning: This
code runs as a native Windows thread. Use of our own functions
needs to be bracket with pth_leave/pth_enter. */
static LRESULT CALLBACK
putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int ret = 0;
int w32rc;
COPYDATASTRUCT *cds;
const char *mapfile;
HANDLE maphd;
PSID mysid = NULL;
PSID mapsid = NULL;
void *data = NULL;
PSECURITY_DESCRIPTOR psd = NULL;
ctrl_t ctrl = NULL;
if (msg != WM_COPYDATA)
{
return DefWindowProc (hwnd, msg, wparam, lparam);
}
cds = (COPYDATASTRUCT*)lparam;
if (cds->dwData != PUTTY_IPC_MAGIC)
return 0; /* Ignore data with the wrong magic. */
mapfile = cds->lpData;
if (!cds->cbData || mapfile[cds->cbData - 1])
return 0; /* Ignore empty and non-properly terminated strings. */
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map file '%s'", mapfile);
npth_unprotect ();
}
maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map handle %p\n", maphd);
npth_unprotect ();
}
if (!maphd || maphd == INVALID_HANDLE_VALUE)
return 0;
npth_protect ();
mysid = w32_get_user_sid ();
if (!mysid)
{
log_error ("error getting my sid\n");
goto leave;
}
w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION,
&mapsid, NULL, NULL, NULL,
&psd);
if (w32rc)
{
log_error ("error getting sid of ssh map file: rc=%d", w32rc);
goto leave;
}
if (DBG_IPC)
{
char *sidstr;
if (!ConvertSidToStringSid (mysid, &sidstr))
sidstr = NULL;
log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
if (!ConvertSidToStringSid (mapsid, &sidstr))
sidstr = NULL;
log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
}
if (!EqualSid (mysid, mapsid))
{
log_error ("ssh map file has a non-matching sid\n");
goto leave;
}
data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (DBG_IPC)
log_debug ("ssh IPC buffer at %p\n", data);
if (!data)
goto leave;
/* log_printhex ("request:", data, 20); */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
goto leave;
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
goto leave;
}
agent_init_default_ctrl (ctrl);
if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
ret = 1; /* Valid ssh message has been constructed. */
agent_deinit_default_ctrl (ctrl);
/* log_printhex (" reply:", data, 20); */
leave:
xfree (ctrl);
if (data)
UnmapViewOfFile (data);
xfree (mapsid);
if (psd)
LocalFree (psd);
xfree (mysid);
CloseHandle (maphd);
npth_unprotect ();
return ret;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* The thread handling Putty's IPC requests. */
static void *
putty_message_thread (void *arg)
{
WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
NULL, NULL, NULL, NULL, NULL, "Pageant"};
HWND hwnd;
MSG msg;
(void)arg;
if (opt.verbose)
log_info ("putty message loop thread started\n");
/* The message loop runs as thread independent from our nPth system.
This also means that we need to make sure that we switch back to
our system before calling any no-windows function. */
npth_unprotect ();
/* First create a window to make sure that a message queue exists
for this thread. */
if (!RegisterClass (&wndwclass))
{
npth_protect ();
log_error ("error registering Pageant window class");
return NULL;
}
hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
0, 0, 0, 0,
HWND_MESSAGE, /* hWndParent */
NULL, /* hWndMenu */
NULL, /* hInstance */
NULL); /* lpParm */
if (!hwnd)
{
npth_protect ();
log_error ("error creating Pageant window");
return NULL;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Back to nPth. */
npth_protect ();
if (opt.verbose)
log_info ("putty message loop thread stopped\n");
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
static void *
do_start_connection_thread (ctrl_t ctrl)
{
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread_std (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* This is the extra socket connection thread's main function. */
static void *
start_connection_thread_extra (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_extra))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 1;
return do_start_connection_thread (ctrl);
}
/* This is the browser socket connection thread's main function. */
static void *
start_connection_thread_browser (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_browser))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 2;
return do_start_connection_thread (ctrl);
}
/* This is the ssh connection thread's main function. */
static void *
start_connection_thread_ssh (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_ssh))
return NULL;
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread_std },
{ "extra", start_connection_thread_extra },
{ "browser", start_connection_thread_browser },
{ "ssh", start_connection_thread_ssh }
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n",
strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
# ifdef HAVE_W32CE_SYSTEM
/* Use a dummy event. */
sigs = 0;
ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
# else
events[0] = get_agent_scd_notify_event ();
events[1] = INVALID_HANDLE_VALUE;
# endif
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
/* On Windows we need to fire up a separate thread to listen for
requests from Putty (an SSH client), so we can replace Putty's
Pageant (its ssh-agent implementation). */
#ifdef HAVE_W32_SYSTEM
if (putty_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
if (ret)
{
log_error ("error spawning putty message loop: %s\n", strerror (ret));
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (listen_fd_extra != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_extra), &fdset);
if (FD2INT (listen_fd_extra) > nfd)
nfd = FD2INT (listen_fd_extra);
}
if (listen_fd_browser != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_browser), &fdset);
if (FD2INT (listen_fd_browser) > nfd)
nfd = FD2INT (listen_fd_browser);
}
if (listen_fd_ssh != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_ssh), &fdset);
if (FD2INT (listen_fd_ssh) > nfd)
nfd = FD2INT (listen_fd_ssh);
}
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
listentbl[1].l_fd = listen_fd_extra;
listentbl[2].l_fd = listen_fd_browser;
listentbl[3].l_fd = listen_fd_ssh;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if (events_set & 1)
agent_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else if ( !(ctrl->session_env = session_env_new ()))
{
log_error ("error allocating session env block for %s: %s\n",
listentbl[idx].name, strerror (errno) );
xfree (ctrl);
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
separate thread so that check_own_thread can be called from the
timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
return;
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether an agent is available and running. Prints an
error if not. If SILENT is true, no messages are printed.
Returns 0 if the agent is running. */
static int
check_for_running_agent (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no gpg-agent running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("gpg-agent running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/agent/pksign.c b/agent/pksign.c
index 8e88deecc..3474f9434 100644
--- a/agent/pksign.c
+++ b/agent/pksign.c
@@ -1,623 +1,623 @@
/* pksign.c - public key signing (well, actually using a secret key)
* Copyright (C) 2001-2004, 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2004, 2010, 2013 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include "agent.h"
#include "../common/i18n.h"
static int
do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash,
int raw_value)
{
gcry_sexp_t hash;
int rc;
if (!raw_value)
{
const char *s;
char tmp[16+1];
int i;
s = gcry_md_algo_name (algo);
if (!s || strlen (s) >= 16)
{
hash = NULL;
rc = gpg_error (GPG_ERR_DIGEST_ALGO);
}
else
{
for (i=0; s[i]; i++)
tmp[i] = ascii_tolower (s[i]);
tmp[i] = '\0';
rc = gcry_sexp_build (&hash, NULL,
"(data (flags pkcs1) (hash %s %b))",
tmp, (int)mdlen, md);
}
}
else
{
gcry_mpi_t mpi;
rc = gcry_mpi_scan (&mpi, GCRYMPI_FMT_USG, md, mdlen, NULL);
if (!rc)
{
rc = gcry_sexp_build (&hash, NULL,
"(data (flags raw) (value %m))",
mpi);
gcry_mpi_release (mpi);
}
else
hash = NULL;
}
*r_hash = hash;
return rc;
}
/* Return the number of bits of the Q parameter from the DSA key
KEY. */
static unsigned int
get_dsa_qbits (gcry_sexp_t key)
{
gcry_sexp_t l1, l2;
gcry_mpi_t q;
unsigned int nbits;
l1 = gcry_sexp_find_token (key, "private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "protected-private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "shadowed-private-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (key, "public-key", 0);
if (!l1)
return 0; /* Does not contain a key object. */
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = gcry_sexp_find_token (l2, "q", 1);
gcry_sexp_release (l2);
if (!l1)
return 0; /* Invalid object. */
q = gcry_sexp_nth_mpi (l1, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l1);
if (!q)
return 0; /* Missing value. */
nbits = gcry_mpi_get_nbits (q);
gcry_mpi_release (q);
return nbits;
}
/* Return an appropriate hash algorithm to be used with RFC-6979 for a
message digest of length MDLEN. Although a fallback of SHA-256 is
used the current implementation in Libgcrypt will reject a hash
algorithm which does not match the length of the message. */
static const char *
rfc6979_hash_algo_string (size_t mdlen)
{
switch (mdlen)
{
case 20: return "sha1";
case 28: return "sha224";
case 32: return "sha256";
case 48: return "sha384";
case 64: return "sha512";
default: return "sha256";
}
}
/* Encode a message digest for use with the EdDSA algorithm
(i.e. curve Ed25519). */
static gpg_error_t
do_encode_eddsa (const byte *md, size_t mdlen, gcry_sexp_t *r_hash)
{
gpg_error_t err;
gcry_sexp_t hash;
*r_hash = NULL;
err = gcry_sexp_build (&hash, NULL,
"(data(flags eddsa)(hash-algo sha512)(value %b))",
(int)mdlen, md);
if (!err)
*r_hash = hash;
return err;
}
/* Encode a message digest for use with an DSA algorithm. */
static gpg_error_t
do_encode_dsa (const byte *md, size_t mdlen, int pkalgo, gcry_sexp_t pkey,
gcry_sexp_t *r_hash)
{
gpg_error_t err;
gcry_sexp_t hash;
unsigned int qbits;
*r_hash = NULL;
if (pkalgo == GCRY_PK_ECDSA)
qbits = gcry_pk_get_nbits (pkey);
else if (pkalgo == GCRY_PK_DSA)
qbits = get_dsa_qbits (pkey);
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (pkalgo == GCRY_PK_DSA && (qbits%8))
{
/* FIXME: We check the QBITS but print a message about the hash
length. */
log_error (_("DSA requires the hash length to be a"
" multiple of 8 bits\n"));
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* Don't allow any Q smaller than 160 bits. We don't want someone
to issue signatures from a key with a 16-bit Q or something like
that, which would look correct but allow trivial forgeries. Yes,
I know this rules out using MD5 with DSA. ;) */
if (qbits < 160)
{
log_error (_("%s key uses an unsafe (%u bit) hash\n"),
gcry_pk_algo_name (pkalgo), qbits);
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* ECDSA 521 is special has it is larger than the largest hash
we have (SHA-512). Thus we change the size for further
processing to 512. */
if (pkalgo == GCRY_PK_ECDSA && qbits > 512)
qbits = 512;
/* Check if we're too short. Too long is safe as we'll
automatically left-truncate. */
if (mdlen < qbits/8)
{
log_error (_("a %zu bit hash is not valid for a %u bit %s key\n"),
mdlen*8,
gcry_pk_get_nbits (pkey),
gcry_pk_algo_name (pkalgo));
return gpg_error (GPG_ERR_INV_LENGTH);
}
/* Truncate. */
if (mdlen > qbits/8)
mdlen = qbits/8;
/* Create the S-expression. */
err = gcry_sexp_build (&hash, NULL,
"(data (flags rfc6979) (hash %s %b))",
rfc6979_hash_algo_string (mdlen),
(int)mdlen, md);
if (!err)
*r_hash = hash;
return err;
}
/* Special version of do_encode_md to take care of pkcs#1 padding.
For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does
not know about this special scheme. Fixme: We should have a
pkcs1-only-padding flag for Libgcrypt. */
static int
do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits,
gcry_sexp_t *r_hash)
{
int rc;
gcry_sexp_t hash;
unsigned char *frame;
size_t i, n, nframe;
nframe = (nbits+7) / 8;
if ( !mdlen || mdlen + 8 + 4 > nframe )
{
/* Can't encode this hash into a frame of size NFRAME. */
return gpg_error (GPG_ERR_TOO_SHORT);
}
frame = xtrymalloc (nframe);
if (!frame)
return gpg_error_from_syserror ();
/* Assemble the pkcs#1 block type 1. */
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = nframe - mdlen - 3 ;
log_assert (i >= 8); /* At least 8 bytes of padding. */
memset (frame+n, 0xff, i );
n += i;
frame[n++] = 0;
memcpy (frame+n, md, mdlen );
n += mdlen;
log_assert (n == nframe);
/* Create the S-expression. */
rc = gcry_sexp_build (&hash, NULL,
"(data (flags raw) (value %b))",
(int)nframe, frame);
xfree (frame);
*r_hash = hash;
return rc;
}
/* SIGN whatever information we have accumulated in CTRL and return
* the signature S-expression. LOOKUP is an optional function to
* provide a way for lower layers to ask for the caching TTL. If a
* CACHE_NONCE is given that cache item is first tried to get a
* passphrase. If OVERRIDEDATA is not NULL, OVERRIDEDATALEN bytes
* from this buffer are used instead of the data in CTRL. The
* override feature is required to allow the use of Ed25519 with ssh
* because Ed25519 does the hashing itself. */
gpg_error_t
agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen)
{
gpg_error_t err = 0;
gcry_sexp_t s_skey = NULL;
gcry_sexp_t s_sig = NULL;
gcry_sexp_t s_hash = NULL;
gcry_sexp_t s_pkey = NULL;
unsigned char *shadow_info = NULL;
int no_shadow_info = 0;
const unsigned char *data;
int datalen;
int check_signature = 0;
if (overridedata)
{
data = overridedata;
datalen = overridedatalen;
}
else
{
data = ctrl->digest.value;
datalen = ctrl->digest.valuelen;
}
if (!ctrl->have_keygrip)
return gpg_error (GPG_ERR_NO_SECKEY);
err = agent_key_from_file (ctrl, cache_nonce, desc_text, ctrl->keygrip,
&shadow_info, cache_mode, lookup_ttl,
&s_skey, NULL);
if (gpg_err_code (err) == GPG_ERR_NO_SECKEY)
no_shadow_info = 1;
else if (err)
{
log_error ("failed to read the secret key\n");
goto leave;
}
if (shadow_info || no_shadow_info)
{
/* Divert operation to the smartcard. With NO_SHADOW_INFO set
* we don't have the keystub but we want to see whether the key
* is on the active card. */
size_t len;
unsigned char *buf = NULL;
int key_type;
int is_RSA = 0;
int is_ECDSA = 0;
int is_EdDSA = 0;
if (no_shadow_info)
{
/* Try to get the public key from the card or fail with the
* original NO_SECKEY error. We also write a stub file (we
* are here only because no stub exists). */
char *serialno;
unsigned char *pkbuf = NULL;
size_t pkbuflen;
char hexgrip[2*KEYGRIP_LEN+1];
char *keyref;
if (agent_card_serialno (ctrl, &serialno, NULL))
{
- /* No card availabale or error reading the card. */
+ /* No card available or error reading the card. */
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
bin2hex (ctrl->keygrip, KEYGRIP_LEN, hexgrip);
if (agent_card_readkey (ctrl, hexgrip, &pkbuf, &keyref))
{
/* No such key on the card. */
xfree (serialno);
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
err = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (err)
{
xfree (serialno);
xfree (pkbuf);
xfree (keyref);
log_error ("%s: corrupted key returned by scdaemon\n", __func__);
goto leave;
}
if (keyref)
agent_write_shadow_key (ctrl->keygrip, serialno, keyref, pkbuf, 0);
xfree (serialno);
xfree (pkbuf);
xfree (keyref);
}
else
{
/* Get the public key from the stub file. */
err = agent_public_key_from_file (ctrl, ctrl->keygrip, &s_pkey);
if (err)
{
log_error ("failed to read the public key\n");
goto leave;
}
}
if (agent_is_eddsa_key (s_pkey))
is_EdDSA = 1;
else
{
key_type = agent_is_dsa_key (s_pkey);
if (key_type == 0)
is_RSA = 1;
else if (key_type == GCRY_PK_ECDSA)
is_ECDSA = 1;
}
{
char *desc2 = NULL;
if (desc_text)
agent_modify_description (desc_text, NULL, s_pkey, &desc2);
err = divert_pksign (ctrl, desc2? desc2 : desc_text,
ctrl->keygrip,
data, datalen,
ctrl->digest.algo,
shadow_info, &buf, &len);
xfree (desc2);
}
if (err)
{
log_error ("smartcard signing failed: %s\n", gpg_strerror (err));
goto leave;
}
if (is_RSA)
{
unsigned char *p = buf;
check_signature = 1;
/*
* Smartcard returns fixed-size data, which is good for
* PKCS1. If variable-size unsigned MPI is needed, remove
* zeros.
*/
if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1
|| ctrl->digest.raw_value)
{
int i;
for (i = 0; i < len - 1; i++)
if (p[i])
break;
p += i;
len -= i;
}
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%b)))",
(int)len, p);
}
else if (is_EdDSA)
{
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(eddsa(r%b)(s%b)))",
(int)len/2, buf, (int)len/2, buf + len/2);
}
else if (is_ECDSA)
{
unsigned char *r_buf, *s_buf;
int r_buflen, s_buflen;
int i;
r_buflen = s_buflen = len/2;
/*
* Smartcard returns fixed-size data. For ECDSA signature,
* variable-size unsigned MPI is assumed, thus, remove
* zeros.
*/
r_buf = buf;
for (i = 0; i < r_buflen - 1; i++)
if (r_buf[i])
break;
r_buf += i;
r_buflen -= i;
s_buf = buf + len/2;
for (i = 0; i < s_buflen - 1; i++)
if (s_buf[i])
break;
s_buf += i;
s_buflen -= i;
err = gcry_sexp_build (&s_sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
r_buflen, r_buf,
s_buflen, s_buf);
}
else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
xfree (buf);
if (err)
{
log_error ("failed to convert sigbuf returned by divert_pksign "
"into S-Exp: %s", gpg_strerror (err));
goto leave;
}
}
else
{
/* No smartcard, but a private key (in S_SKEY). */
int dsaalgo = 0;
/* Put the hash into a sexp */
if (agent_is_eddsa_key (s_skey))
err = do_encode_eddsa (data, datalen,
&s_hash);
else if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
err = do_encode_raw_pkcs1 (data, datalen,
gcry_pk_get_nbits (s_skey),
&s_hash);
else if ( (dsaalgo = agent_is_dsa_key (s_skey)) )
err = do_encode_dsa (data, datalen,
dsaalgo, s_skey,
&s_hash);
else
err = do_encode_md (data, datalen,
ctrl->digest.algo,
&s_hash,
ctrl->digest.raw_value);
if (err)
goto leave;
if (dsaalgo == 0 && GCRYPT_VERSION_NUMBER < 0x010700)
{
/* It's RSA and Libgcrypt < 1.7 */
check_signature = 1;
}
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("skey", s_skey);
gcry_log_debugsxp ("hash", s_hash);
}
/* sign */
err = gcry_pk_sign (&s_sig, s_hash, s_skey);
if (err)
{
log_error ("signing failed: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
gcry_log_debugsxp ("rslt", s_sig);
}
/* Check that the signature verification worked and nothing is
* fooling us e.g. by a bug in the signature create code or by
* deliberately introduced faults. Because Libgcrypt 1.7 does this
* for RSA internally there is no need to do it here again. We do
* this always for card based RSA keys, though. */
if (check_signature)
{
gcry_sexp_t sexp_key = s_pkey? s_pkey: s_skey;
if (s_hash == NULL)
{
if (ctrl->digest.algo == MD_USER_TLS_MD5SHA1)
err = do_encode_raw_pkcs1 (data, datalen,
gcry_pk_get_nbits (sexp_key), &s_hash);
else
err = do_encode_md (data, datalen, ctrl->digest.algo, &s_hash,
ctrl->digest.raw_value);
}
if (!err)
err = gcry_pk_verify (s_sig, s_hash, sexp_key);
if (err)
{
log_error (_("checking created signature failed: %s\n"),
gpg_strerror (err));
gcry_sexp_release (s_sig);
s_sig = NULL;
}
}
leave:
*signature_sexp = s_sig;
gcry_sexp_release (s_pkey);
gcry_sexp_release (s_skey);
gcry_sexp_release (s_hash);
xfree (shadow_info);
return err;
}
/* SIGN whatever information we have accumulated in CTRL and write it
* back to OUTFP. If a CACHE_NONCE is given that cache item is first
* tried to get a passphrase. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode)
{
gpg_error_t err;
gcry_sexp_t s_sig = NULL;
char *buf = NULL;
size_t len = 0;
err = agent_pksign_do (ctrl, cache_nonce, desc_text, &s_sig, cache_mode,
NULL, NULL, 0);
if (err)
goto leave;
len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (len);
buf = xtrymalloc (len);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = gcry_sexp_sprint (s_sig, GCRYSEXP_FMT_CANON, buf, len);
log_assert (len);
put_membuf (outbuf, buf, len);
leave:
gcry_sexp_release (s_sig);
xfree (buf);
return err;
}
diff --git a/agent/w32main.c b/agent/w32main.c
index 0e3927ad2..143106079 100644
--- a/agent/w32main.c
+++ b/agent/w32main.c
@@ -1,306 +1,306 @@
/* w32main.c - W32 main entry pint and taskbar support for the GnuPG Agent
* Copyright (C) 2007 Free Software Foundation, Inc.
* Copyright 1996, 1998 Alexandre Julliard
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#ifndef HAVE_W32_SYSTEM
#error This module is only useful for the W32 version of gpg-agent
#endif
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <windows.h>
#include "../common/util.h"
#include "w32main.h"
/* The instance handle has received by WinMain. */
static HINSTANCE glob_hinst;
static HWND glob_hwnd;
/* Build an argv array from the command in CMDLINE. RESERVED is the
number of args to reserve before the first one. This code is based
on Alexandre Julliard's LGPLed wine-0.9.34/dlls/kernel32/process.c
and modified to fit into our framework. The function returns NULL
- on error; on success an arry with the argiments is returned. This
- array has been allocaqted using a plain malloc (and not the usual
+ on error; on success an array with the arguments is returned. This
+ array has been allocated using a plain malloc (and not the usual
xtrymalloc). */
static char **
build_argv (char *cmdline_arg, int reserved)
{
int argc;
char **argv;
char *cmdline, *s, *arg, *d;
int in_quotes, bs_count;
cmdline = malloc (strlen (cmdline_arg) + 1);
if (!cmdline)
return NULL;
strcpy (cmdline, cmdline_arg);
/* First determine the required size of the array. */
argc = reserved + 1;
bs_count = 0;
in_quotes = 0;
s = cmdline;
for (;;)
{
if ( !*s || ((*s==' ' || *s=='\t') && !in_quotes)) /* A space. */
{
argc++;
/* Skip the remaining spaces. */
while (*s==' ' || *s=='\t')
s++;
if (!*s)
break;
bs_count = 0;
}
else if (*s=='\\')
{
bs_count++;
s++;
}
else if ( (*s == '\"') && !(bs_count & 1))
{
/* Unescaped '\"' */
in_quotes = !in_quotes;
bs_count=0;
s++;
}
else /* A regular character. */
{
bs_count = 0;
s++;
}
}
argv = xtrymalloc (argc * sizeof *argv);
if (!argv)
{
xfree (cmdline);
return NULL;
}
/* Now actually parse the command line. */
argc = reserved;
bs_count = 0;
in_quotes=0;
arg = d = s = cmdline;
while (*s)
{
if ((*s==' ' || *s=='\t') && !in_quotes)
{
/* Close the argument and copy it. */
*d = 0;
argv[argc++] = arg;
/* Skip the remaining spaces. */
do
s++;
while (*s==' ' || *s=='\t');
/* Start with a new argument */
arg = d = s;
bs_count = 0;
}
else if (*s=='\\')
{
*d++ = *s++;
bs_count++;
}
else if (*s=='\"')
{
if ( !(bs_count & 1) )
{
/* Preceded by an even number of backslashes, this is
half that number of backslashes, plus a '\"' which we
discard. */
d -= bs_count/2;
s++;
in_quotes = !in_quotes;
}
else
{
/* Preceded by an odd number of backslashes, this is
half that number of backslashes followed by a '\"'. */
d = d - bs_count/2 - 1;
*d++ ='\"';
s++;
}
bs_count=0;
}
else /* A regular character. */
{
*d++ = *s++;
bs_count = 0;
}
}
if (*arg)
{
*d = 0;
argv[argc++] = arg;
}
argv[argc] = NULL;
return argv;
}
/* Our window message processing function. */
static LRESULT CALLBACK
wndw_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_USER:
fprintf (stderr,"%s: received WM_%s\n", __func__, "USER" );
break;
}
return DefWindowProc (hwnd, msg, wparam, lparam);
}
/* This function is called to do some fast event polling and
processing. */
void
w32_poll_events (void)
{
/* MSG msg; */
/* fprintf (stderr,"%s: enter\n", __func__); */
/* while (PeekMessage (&msg, glob_hwnd, 0, 0, PM_REMOVE)) */
/* { */
/* DispatchMessage (&msg); */
/* } */
/* fprintf (stderr,"%s: leave\n", __func__); */
}
static void *
handle_taskbar (void *ctx)
{
WNDCLASS wndwclass = {0, wndw_proc, 0, 0, glob_hinst,
0, 0, 0, 0, "gpg-agent"};
NOTIFYICONDATA nid;
HWND hwnd;
MSG msg;
int rc;
if (!RegisterClass (&wndwclass))
{
log_error ("error registering window class\n");
ExitThread (0);
}
hwnd = CreateWindow ("gpg-agent", "gpg-agent",
0, 0, 0, 0, 0,
NULL, NULL, glob_hinst, NULL);
if (!hwnd)
{
log_error ("error creating main window\n");
ExitThread (0);
}
glob_hwnd = hwnd;
UpdateWindow (hwnd);
memset (&nid, 0, sizeof nid);
nid.cbSize = sizeof (nid);
nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
nid.uCallbackMessage = WM_USER;
nid.hWnd = glob_hwnd;
nid.uID = 1;
nid.hIcon = LoadIcon (glob_hinst, MAKEINTRESOURCE (1));
mem2str (nid.szTip, GPG_AGENT_NAME " version "PACKAGE_VERSION,
sizeof nid.szTip);
Shell_NotifyIcon (NIM_ADD, &nid);
DestroyIcon (nid.hIcon);
fprintf (stderr, "%s: enter\n", __func__);
while ( (rc=GetMessage (&msg, hwnd, 0, 0)) )
{
if (rc == -1)
{
log_error ("getMessage failed: %s\n", w32_strerror (-1));
break;
}
TranslateMessage (&msg);
DispatchMessage (&msg);
}
fprintf (stderr,"%s: leave\n", __func__);
ExitThread (0);
return NULL;
}
/* This function initializes the Window system and sets up the taskbar
icon. We only have very limited GUI support just to give the
taskbar icon a little bit of life. This function is called once to
fire up the icon. */
int
w32_setup_taskbar (void)
{
SECURITY_ATTRIBUTES sa;
DWORD tid;
HANDLE th;
memset (&sa, 0, sizeof sa);
sa.nLength = sizeof sa;
sa.bInheritHandle = FALSE;
fprintf (stderr,"creating thread for the taskbar_event_loop...\n");
th = CreateThread (&sa, 128*1024,
(LPTHREAD_START_ROUTINE)handle_taskbar,
NULL, 0, &tid);
fprintf (stderr,"created thread %p tid=%d\n", th, (int)tid);
CloseHandle (th);
return 0;
}
/* The main entry point for the Windows version. We save away all GUI
related stuff, parse the command line and finally call the real
main. */
int WINAPI
WinMain (HINSTANCE hinst, HINSTANCE hprev, LPSTR cmdline, int showcmd)
{
char **argv;
int argc;
/* We use the GetCommandLine function because that also includes the
program name in contrast to the CMDLINE arg. */
argv = build_argv (GetCommandLineA (), 0);
if (!argv)
return 2; /* Can't do much about a malloc failure. */
for (argc=0; argv[argc]; argc++)
;
glob_hinst = hinst;
return w32_main (argc, argv);
}
diff --git a/common/argparse.c b/common/argparse.c
index db0b7e079..c2ba52e3f 100644
--- a/common/argparse.c
+++ b/common/argparse.c
@@ -1,1660 +1,1660 @@
/* argparse.c - Argument Parser for option handling
* Copyright (C) 1997-2001, 2006-2008, 2013-2017 Werner Koch
* Copyright (C) 1998-2001, 2006-2008, 2012 Free Software Foundation, Inc.
* Copyright (C) 2015-2017 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1+
*/
/* This file may be used as part of GnuPG or standalone. A GnuPG
build is detected by the presence of the macro GNUPG_MAJOR_VERSION.
- Some feature are only availalbe in the GnuPG build mode.
+ Some feature are only available in the GnuPG build mode.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <limits.h>
#include <errno.h>
#ifdef GNUPG_MAJOR_VERSION
# include "util.h"
# include "common-defs.h"
# include "i18n.h"
# include "mischelp.h"
# include "stringhelp.h"
# include "logging.h"
# include "utf8conv.h"
#endif /*GNUPG_MAJOR_VERSION*/
#include "argparse.h"
/* GnuPG uses GPLv3+ but a standalone version of this defaults to
GPLv2+ because that is the license of this file. Change this if
you include it in a program which uses GPLv3. If you don't want to
set a copyright string for your usage() you may also hardcode it
here. */
#ifndef GNUPG_MAJOR_VERSION
# define ARGPARSE_GPL_VERSION 2
# define ARGPARSE_CRIGHT_STR "Copyright (C) YEAR NAME"
#else /* Used by GnuPG */
# define ARGPARSE_GPL_VERSION 3
# define ARGPARSE_CRIGHT_STR "Copyright (C) 2018 Free Software Foundation, Inc."
#endif /*GNUPG_MAJOR_VERSION*/
/* Replacements for standalone builds. */
#ifndef GNUPG_MAJOR_VERSION
# ifndef _
# define _(a) (a)
# endif
# ifndef DIM
# define DIM(v) (sizeof(v)/sizeof((v)[0]))
# endif
# define xtrymalloc(a) malloc ((a))
# define xtryrealloc(a,b) realloc ((a), (b))
# define xtrystrdup(a) strdup ((a))
# define xfree(a) free ((a))
# define log_error my_log_error
# define log_bug my_log_bug
# define trim_spaces(a) my_trim_spaces ((a))
# define map_static_macro_string(a) (a)
#endif /*!GNUPG_MAJOR_VERSION*/
#define ARGPARSE_STR(v) #v
#define ARGPARSE_STR2(v) ARGPARSE_STR(v)
/* Replacements for standalone builds. */
#ifndef GNUPG_MAJOR_VERSION
static void
my_log_error (const char *fmt, ...)
{
va_list arg_ptr ;
va_start (arg_ptr, fmt);
fprintf (stderr, "%s: ", strusage (11));
vfprintf (stderr, fmt, arg_ptr);
va_end (arg_ptr);
}
static void
my_log_bug (const char *fmt, ...)
{
va_list arg_ptr ;
va_start (arg_ptr, fmt);
fprintf (stderr, "%s: Ohhhh jeeee: ", strusage (11));
vfprintf (stderr, fmt, arg_ptr);
va_end (arg_ptr);
abort ();
}
/* Return true if the native charset is utf-8. */
static int
is_native_utf8 (void)
{
return 1;
}
static char *
my_trim_spaces (char *str)
{
char *string, *p, *mark;
string = str;
/* Find first non space character. */
for (p=string; *p && isspace (*(unsigned char*)p) ; p++)
;
/* Move characters. */
for ((mark = NULL); (*string = *p); string++, p++)
if (isspace (*(unsigned char*)p))
{
if (!mark)
mark = string;
}
else
mark = NULL;
if (mark)
*mark = '\0' ; /* Remove trailing spaces. */
return str ;
}
#endif /*!GNUPG_MAJOR_VERSION*/
/*********************************
* @Summary arg_parse
* #include "argparse.h"
*
* typedef struct {
* char *argc; pointer to argc (value subject to change)
* char ***argv; pointer to argv (value subject to change)
* unsigned flags; Global flags (DO NOT CHANGE)
* int err; print error about last option
* 1 = warning, 2 = abort
* int r_opt; return option
* int r_type; type of return value (0 = no argument found)
* union {
* int ret_int;
* long ret_long
* ulong ret_ulong;
* char *ret_str;
* } r; Return values
* struct {
* int idx;
* const char *last;
* void *aliases;
* } internal; DO NOT CHANGE
* } ARGPARSE_ARGS;
*
* typedef struct {
* int short_opt;
* const char *long_opt;
* unsigned flags;
* } ARGPARSE_OPTS;
*
* int arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts );
*
* @Description
* This is my replacement for getopt(). See the example for a typical usage.
* Global flags are:
* Bit 0 : Do not remove options form argv
* Bit 1 : Do not stop at last option but return other args
* with r_opt set to -1.
* Bit 2 : Assume options and real args are mixed.
* Bit 3 : Do not use -- to stop option processing.
* Bit 4 : Do not skip the first arg.
* Bit 5 : allow usage of long option with only one dash
* Bit 6 : ignore --version
* all other bits must be set to zero, this value is modified by the
* function, so assume this is write only.
* Local flags (for each option):
* Bit 2-0 : 0 = does not take an argument
* 1 = takes int argument
* 2 = takes string argument
* 3 = takes long argument
* 4 = takes ulong argument
* Bit 3 : argument is optional (r_type will the be set to 0)
* Bit 4 : allow 0x etc. prefixed values.
* Bit 6 : Ignore this option
* Bit 7 : This is a command and not an option
* You stop the option processing by setting opts to NULL, the function will
* then return 0.
* @Return Value
* Returns the args.r_opt or 0 if ready
* r_opt may be -2/-7 to indicate an unknown option/command.
* @See Also
* ArgExpand
* @Notes
* You do not need to process the options 'h', '--help' or '--version'
* because this function includes standard help processing; but if you
* specify '-h', '--help' or '--version' you have to do it yourself.
* The option '--' stops argument processing; if bit 1 is set the function
* continues to return normal arguments.
* To process float args or unsigned args you must use a string args and do
* the conversion yourself.
* @Example
*
* ARGPARSE_OPTS opts[] = {
* { 'v', "verbose", 0 },
* { 'd', "debug", 0 },
* { 'o', "output", 2 },
* { 'c', "cross-ref", 2|8 },
* { 'm', "my-option", 1|8 },
* { 300, "ignored-long-option, ARGPARSE_OP_IGNORE},
* { 500, "have-no-short-option-for-this-long-option", 0 },
* {0} };
* ARGPARSE_ARGS pargs = { &argc, &argv, 0 }
*
* while( ArgParse( &pargs, &opts) ) {
* switch( pargs.r_opt ) {
* case 'v': opt.verbose++; break;
* case 'd': opt.debug++; break;
* case 'o': opt.outfile = pargs.r.ret_str; break;
* case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
* case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
* case 500: opt.a_long_one++; break
* default : pargs.err = 1; break; -- force warning output --
* }
* }
* if( argc > 1 )
* log_fatal( "Too many args");
*
*/
typedef struct alias_def_s *ALIAS_DEF;
struct alias_def_s {
ALIAS_DEF next;
char *name; /* malloced buffer with name, \0, value */
const char *value; /* ptr into name */
};
/* Object to store the names for the --ignore-invalid-option option.
This is a simple linked list. */
typedef struct iio_item_def_s *IIO_ITEM_DEF;
struct iio_item_def_s
{
IIO_ITEM_DEF next;
char name[1]; /* String with the long option name. */
};
static const char *(*strusage_handler)( int ) = NULL;
static int (*custom_outfnc) (int, const char *);
static int set_opt_arg(ARGPARSE_ARGS *arg, unsigned flags, char *s);
static void show_help(ARGPARSE_OPTS *opts, unsigned flags);
static void show_version(void);
static int writestrings (int is_error, const char *string, ...)
#if __GNUC__ >= 4
__attribute__ ((sentinel(0)))
#endif
;
void
argparse_register_outfnc (int (*fnc)(int, const char *))
{
custom_outfnc = fnc;
}
/* Write STRING and all following const char * arguments either to
stdout or, if IS_ERROR is set, to stderr. The list of strings must
be terminated by a NULL. */
static int
writestrings (int is_error, const char *string, ...)
{
va_list arg_ptr;
const char *s;
int count = 0;
if (string)
{
s = string;
va_start (arg_ptr, string);
do
{
if (custom_outfnc)
custom_outfnc (is_error? 2:1, s);
else
fputs (s, is_error? stderr : stdout);
count += strlen (s);
}
while ((s = va_arg (arg_ptr, const char *)));
va_end (arg_ptr);
}
return count;
}
static void
flushstrings (int is_error)
{
if (custom_outfnc)
custom_outfnc (is_error? 2:1, NULL);
else
fflush (is_error? stderr : stdout);
}
static void
initialize( ARGPARSE_ARGS *arg, const char *filename, unsigned *lineno )
{
if( !(arg->flags & (1<<15)) )
{
/* Initialize this instance. */
arg->internal.idx = 0;
arg->internal.last = NULL;
arg->internal.inarg = 0;
arg->internal.stopped = 0;
arg->internal.aliases = NULL;
arg->internal.cur_alias = NULL;
arg->internal.iio_list = NULL;
arg->err = 0;
arg->flags |= 1<<15; /* Mark as initialized. */
if ( *arg->argc < 0 )
log_bug ("invalid argument for arg_parse\n");
}
if (arg->err)
{
/* Last option was erroneous. */
const char *s;
if (filename)
{
if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG )
s = _("argument not expected");
else if ( arg->r_opt == ARGPARSE_READ_ERROR )
s = _("read error");
else if ( arg->r_opt == ARGPARSE_KEYWORD_TOO_LONG )
s = _("keyword too long");
else if ( arg->r_opt == ARGPARSE_MISSING_ARG )
s = _("missing argument");
else if ( arg->r_opt == ARGPARSE_INVALID_ARG )
s = _("invalid argument");
else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND )
s = _("invalid command");
else if ( arg->r_opt == ARGPARSE_INVALID_ALIAS )
s = _("invalid alias definition");
else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE )
s = _("out of core");
else
s = _("invalid option");
log_error ("%s:%u: %s\n", filename, *lineno, s);
}
else
{
s = arg->internal.last? arg->internal.last:"[??]";
if ( arg->r_opt == ARGPARSE_MISSING_ARG )
log_error (_("missing argument for option \"%.50s\"\n"), s);
else if ( arg->r_opt == ARGPARSE_INVALID_ARG )
log_error (_("invalid argument for option \"%.50s\"\n"), s);
else if ( arg->r_opt == ARGPARSE_UNEXPECTED_ARG )
log_error (_("option \"%.50s\" does not expect an argument\n"), s);
else if ( arg->r_opt == ARGPARSE_INVALID_COMMAND )
log_error (_("invalid command \"%.50s\"\n"), s);
else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_OPTION )
log_error (_("option \"%.50s\" is ambiguous\n"), s);
else if ( arg->r_opt == ARGPARSE_AMBIGUOUS_COMMAND )
log_error (_("command \"%.50s\" is ambiguous\n"),s );
else if ( arg->r_opt == ARGPARSE_OUT_OF_CORE )
log_error ("%s\n", _("out of core\n"));
else
log_error (_("invalid option \"%.50s\"\n"), s);
}
if (arg->err != ARGPARSE_PRINT_WARNING)
exit (2);
arg->err = 0;
}
/* Zero out the return value union. */
arg->r.ret_str = NULL;
arg->r.ret_long = 0;
}
static void
store_alias( ARGPARSE_ARGS *arg, char *name, char *value )
{
/* TODO: replace this dummy function with a rea one
* and fix the problems IRIX has with (ALIAS_DEV)arg..
* used as lvalue
*/
(void)arg;
(void)name;
(void)value;
#if 0
ALIAS_DEF a = xmalloc( sizeof *a );
a->name = name;
a->value = value;
a->next = (ALIAS_DEF)arg->internal.aliases;
(ALIAS_DEF)arg->internal.aliases = a;
#endif
}
/* Return true if KEYWORD is in the ignore-invalid-option list. */
static int
ignore_invalid_option_p (ARGPARSE_ARGS *arg, const char *keyword)
{
IIO_ITEM_DEF item = arg->internal.iio_list;
for (; item; item = item->next)
if (!strcmp (item->name, keyword))
return 1;
return 0;
}
/* Add the keywords up to the next LF to the list of to be ignored
options. After returning FP will either be at EOF or the next
character read will be the first of a new line. The function
returns 0 on success or true on malloc failure. */
static int
ignore_invalid_option_add (ARGPARSE_ARGS *arg, FILE *fp)
{
IIO_ITEM_DEF item;
int c;
char name[100];
int namelen = 0;
int ready = 0;
enum { skipWS, collectNAME, skipNAME, addNAME} state = skipWS;
while (!ready)
{
c = getc (fp);
if (c == '\n')
ready = 1;
else if (c == EOF)
{
c = '\n';
ready = 1;
}
again:
switch (state)
{
case skipWS:
if (!isascii (c) || !isspace(c))
{
namelen = 0;
state = collectNAME;
goto again;
}
break;
case collectNAME:
if (isspace (c))
{
state = addNAME;
goto again;
}
else if (namelen < DIM(name)-1)
name[namelen++] = c;
else /* Too long. */
state = skipNAME;
break;
case skipNAME:
if (isspace (c))
{
state = skipWS;
goto again;
}
break;
case addNAME:
name[namelen] = 0;
if (!ignore_invalid_option_p (arg, name))
{
item = xtrymalloc (sizeof *item + namelen);
if (!item)
return 1;
strcpy (item->name, name);
item->next = (IIO_ITEM_DEF)arg->internal.iio_list;
arg->internal.iio_list = item;
}
state = skipWS;
goto again;
}
}
return 0;
}
/* Clear the entire ignore-invalid-option list. */
static void
ignore_invalid_option_clear (ARGPARSE_ARGS *arg)
{
IIO_ITEM_DEF item, tmpitem;
for (item = arg->internal.iio_list; item; item = tmpitem)
{
tmpitem = item->next;
xfree (item);
}
arg->internal.iio_list = NULL;
}
/****************
* Get options from a file.
* Lines starting with '#' are comment lines.
* Syntax is simply a keyword and the argument.
* Valid keywords are all keywords from the long_opt list without
* the leading dashes. The special keywords "help", "warranty" and "version"
* are not valid here.
* The special keyword "alias" may be used to store alias definitions,
* which are later expanded like long options.
* The option
* ignore-invalid-option OPTIONNAMEs
* is recognized and updates a list of option which should be ignored if they
* are not defined.
* Caller must free returned strings.
* If called with FP set to NULL command line args are parse instead.
*
* Q: Should we allow the syntax
* keyword = value
* and accept for boolean options a value of 1/0, yes/no or true/false?
* Note: Abbreviation of options is here not allowed.
*/
int
optfile_parse (FILE *fp, const char *filename, unsigned *lineno,
ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
{
int state, i, c;
int idx=0;
char keyword[100];
char *buffer = NULL;
size_t buflen = 0;
int in_alias=0;
int unread_buf[3]; /* We use an int so that we can store EOF. */
int unread_buf_count = 0;
if (!fp) /* Divert to arg_parse() in this case. */
return arg_parse (arg, opts);
initialize (arg, filename, lineno);
/* If the LINENO is zero we assume that we are at the start of a
* file and we skip over a possible Byte Order Mark. */
if (!*lineno)
{
unread_buf[0] = getc (fp);
unread_buf[1] = getc (fp);
unread_buf[2] = getc (fp);
if (unread_buf[0] != 0xef
|| unread_buf[1] != 0xbb
|| unread_buf[2] != 0xbf)
unread_buf_count = 3;
}
/* Find the next keyword. */
state = i = 0;
for (;;)
{
if (unread_buf_count)
c = unread_buf[3 - unread_buf_count--];
else
c = getc (fp);
if (c == '\n' || c== EOF )
{
if ( c != EOF )
++*lineno;
if (state == -1)
break;
else if (state == 2)
{
keyword[i] = 0;
for (i=0; opts[i].short_opt; i++ )
{
if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword))
break;
}
idx = i;
arg->r_opt = opts[idx].short_opt;
if ((opts[idx].flags & ARGPARSE_OPT_IGNORE))
{
state = i = 0;
continue;
}
else if (!opts[idx].short_opt )
{
if (!strcmp (keyword, "ignore-invalid-option"))
{
/* No argument - ignore this meta option. */
state = i = 0;
continue;
}
else if (ignore_invalid_option_p (arg, keyword))
{
/* This invalid option is in the iio list. */
state = i = 0;
continue;
}
arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND)
? ARGPARSE_INVALID_COMMAND
: ARGPARSE_INVALID_OPTION);
}
else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK))
arg->r_type = 0; /* Does not take an arg. */
else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL) )
arg->r_type = 0; /* Arg is optional. */
else
arg->r_opt = ARGPARSE_MISSING_ARG;
break;
}
else if (state == 3)
{
/* No argument found. */
if (in_alias)
arg->r_opt = ARGPARSE_MISSING_ARG;
else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK))
arg->r_type = 0; /* Does not take an arg. */
else if ((opts[idx].flags & ARGPARSE_OPT_OPTIONAL))
arg->r_type = 0; /* No optional argument. */
else
arg->r_opt = ARGPARSE_MISSING_ARG;
break;
}
else if (state == 4)
{
/* Has an argument. */
if (in_alias)
{
if (!buffer)
arg->r_opt = ARGPARSE_UNEXPECTED_ARG;
else
{
char *p;
buffer[i] = 0;
p = strpbrk (buffer, " \t");
if (p)
{
*p++ = 0;
trim_spaces (p);
}
if (!p || !*p)
{
xfree (buffer);
arg->r_opt = ARGPARSE_INVALID_ALIAS;
}
else
{
store_alias (arg, buffer, p);
}
}
}
else if (!(opts[idx].flags & ARGPARSE_TYPE_MASK))
arg->r_opt = ARGPARSE_UNEXPECTED_ARG;
else
{
char *p;
if (!buffer)
{
keyword[i] = 0;
buffer = xtrystrdup (keyword);
if (!buffer)
arg->r_opt = ARGPARSE_OUT_OF_CORE;
}
else
buffer[i] = 0;
if (buffer)
{
trim_spaces (buffer);
p = buffer;
if (*p == '"')
{
/* Remove quotes. */
p++;
if (*p && p[strlen(p)-1] == '\"' )
p[strlen(p)-1] = 0;
}
if (!set_opt_arg (arg, opts[idx].flags, p))
xfree (buffer);
else
gpgrt_annotate_leaked_object (buffer);
}
}
break;
}
else if (c == EOF)
{
ignore_invalid_option_clear (arg);
if (ferror (fp))
arg->r_opt = ARGPARSE_READ_ERROR;
else
arg->r_opt = 0; /* EOF. */
break;
}
state = 0;
i = 0;
}
else if (state == -1)
; /* Skip. */
else if (state == 0 && isascii (c) && isspace(c))
; /* Skip leading white space. */
else if (state == 0 && c == '#' )
state = 1; /* Start of a comment. */
else if (state == 1)
; /* Skip comments. */
else if (state == 2 && isascii (c) && isspace(c))
{
/* Check keyword. */
keyword[i] = 0;
for (i=0; opts[i].short_opt; i++ )
if (opts[i].long_opt && !strcmp (opts[i].long_opt, keyword))
break;
idx = i;
arg->r_opt = opts[idx].short_opt;
if ((opts[idx].flags & ARGPARSE_OPT_IGNORE))
{
state = 1; /* Process like a comment. */
}
else if (!opts[idx].short_opt)
{
if (!strcmp (keyword, "alias"))
{
in_alias = 1;
state = 3;
}
else if (!strcmp (keyword, "ignore-invalid-option"))
{
if (ignore_invalid_option_add (arg, fp))
{
arg->r_opt = ARGPARSE_OUT_OF_CORE;
break;
}
state = i = 0;
++*lineno;
}
else if (ignore_invalid_option_p (arg, keyword))
state = 1; /* Process like a comment. */
else
{
arg->r_opt = ((opts[idx].flags & ARGPARSE_OPT_COMMAND)
? ARGPARSE_INVALID_COMMAND
: ARGPARSE_INVALID_OPTION);
state = -1; /* Skip rest of line and leave. */
}
}
else
state = 3;
}
else if (state == 3)
{
/* Skip leading spaces of the argument. */
if (!isascii (c) || !isspace(c))
{
i = 0;
keyword[i++] = c;
state = 4;
}
}
else if (state == 4)
{
/* Collect the argument. */
if (buffer)
{
if (i < buflen-1)
buffer[i++] = c;
else
{
char *tmp;
size_t tmplen = buflen + 50;
tmp = xtryrealloc (buffer, tmplen);
if (tmp)
{
buflen = tmplen;
buffer = tmp;
buffer[i++] = c;
}
else
{
xfree (buffer);
arg->r_opt = ARGPARSE_OUT_OF_CORE;
break;
}
}
}
else if (i < DIM(keyword)-1)
keyword[i++] = c;
else
{
size_t tmplen = DIM(keyword) + 50;
buffer = xtrymalloc (tmplen);
if (buffer)
{
buflen = tmplen;
memcpy(buffer, keyword, i);
buffer[i++] = c;
}
else
{
arg->r_opt = ARGPARSE_OUT_OF_CORE;
break;
}
}
}
else if (i >= DIM(keyword)-1)
{
arg->r_opt = ARGPARSE_KEYWORD_TOO_LONG;
state = -1; /* Skip rest of line and leave. */
}
else
{
keyword[i++] = c;
state = 2;
}
}
return arg->r_opt;
}
static int
find_long_option( ARGPARSE_ARGS *arg,
ARGPARSE_OPTS *opts, const char *keyword )
{
int i;
size_t n;
(void)arg;
/* Would be better if we can do a binary search, but it is not
possible to reorder our option table because we would mess
up our help strings - What we can do is: Build a nice option
lookup table when this function is first invoked */
if( !*keyword )
return -1;
for(i=0; opts[i].short_opt; i++ )
if( opts[i].long_opt && !strcmp( opts[i].long_opt, keyword) )
return i;
#if 0
{
ALIAS_DEF a;
/* see whether it is an alias */
for( a = args->internal.aliases; a; a = a->next ) {
if( !strcmp( a->name, keyword) ) {
/* todo: must parse the alias here */
args->internal.cur_alias = a;
return -3; /* alias available */
}
}
}
#endif
/* not found, see whether it is an abbreviation */
/* aliases may not be abbreviated */
n = strlen( keyword );
for(i=0; opts[i].short_opt; i++ ) {
if( opts[i].long_opt && !strncmp( opts[i].long_opt, keyword, n ) ) {
int j;
for(j=i+1; opts[j].short_opt; j++ ) {
if( opts[j].long_opt
&& !strncmp( opts[j].long_opt, keyword, n )
&& !(opts[j].short_opt == opts[i].short_opt
&& opts[j].flags == opts[i].flags ) )
return -2; /* abbreviation is ambiguous */
}
return i;
}
}
return -1; /* Not found. */
}
int
arg_parse( ARGPARSE_ARGS *arg, ARGPARSE_OPTS *opts)
{
int idx;
int argc;
char **argv;
char *s, *s2;
int i;
/* Fill in missing standard options: help, version, warranty and
* dump-options. */
ARGPARSE_OPTS help_opt
= ARGPARSE_s_n (ARGPARSE_SHORTOPT_HELP, "help", "@");
ARGPARSE_OPTS version_opt
= ARGPARSE_s_n (ARGPARSE_SHORTOPT_VERSION, "version", "@");
ARGPARSE_OPTS warranty_opt
= ARGPARSE_s_n (ARGPARSE_SHORTOPT_WARRANTY, "warranty", "@");
ARGPARSE_OPTS dump_options_opt
= ARGPARSE_s_n(ARGPARSE_SHORTOPT_DUMP_OPTIONS, "dump-options", "@");
int seen_help = 0;
int seen_version = 0;
int seen_warranty = 0;
int seen_dump_options = 0;
i = 0;
while (opts[i].short_opt)
{
if (opts[i].long_opt)
{
if (!strcmp(opts[i].long_opt, help_opt.long_opt))
seen_help = 1;
else if (!strcmp(opts[i].long_opt, version_opt.long_opt))
seen_version = 1;
else if (!strcmp(opts[i].long_opt, warranty_opt.long_opt))
seen_warranty = 1;
else if (!strcmp(opts[i].long_opt, dump_options_opt.long_opt))
seen_dump_options = 1;
}
i++;
}
if (! seen_help)
opts[i++] = help_opt;
if (! seen_version)
opts[i++] = version_opt;
if (! seen_warranty)
opts[i++] = warranty_opt;
if (! seen_dump_options)
opts[i++] = dump_options_opt;
initialize( arg, NULL, NULL );
argc = *arg->argc;
argv = *arg->argv;
idx = arg->internal.idx;
if (!idx && argc && !(arg->flags & ARGPARSE_FLAG_ARG0))
{
/* Skip the first argument. */
argc--; argv++; idx++;
}
next_one:
if (!argc)
{
/* No more args. */
arg->r_opt = 0;
goto leave; /* Ready. */
}
s = *argv;
arg->internal.last = s;
if (arg->internal.stopped && (arg->flags & ARGPARSE_FLAG_ALL))
{
arg->r_opt = ARGPARSE_IS_ARG; /* Not an option but an argument. */
arg->r_type = 2;
arg->r.ret_str = s;
argc--; argv++; idx++; /* set to next one */
}
else if( arg->internal.stopped )
{
arg->r_opt = 0;
goto leave; /* Ready. */
}
else if ( *s == '-' && s[1] == '-' )
{
/* Long option. */
char *argpos;
arg->internal.inarg = 0;
if (!s[2] && !(arg->flags & ARGPARSE_FLAG_NOSTOP))
{
/* Stop option processing. */
arg->internal.stopped = 1;
arg->flags |= ARGPARSE_FLAG_STOP_SEEN;
argc--; argv++; idx++;
goto next_one;
}
argpos = strchr( s+2, '=' );
if ( argpos )
*argpos = 0;
i = find_long_option ( arg, opts, s+2 );
if ( argpos )
*argpos = '=';
if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_HELP)
show_help (opts, arg->flags);
else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_VERSION)
{
if (!(arg->flags & ARGPARSE_FLAG_NOVERSION))
{
show_version ();
exit(0);
}
}
else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_WARRANTY)
{
writestrings (0, strusage (16), "\n", NULL);
exit (0);
}
else if (i > 0 && opts[i].short_opt == ARGPARSE_SHORTOPT_DUMP_OPTIONS)
{
for (i=0; opts[i].short_opt; i++ )
{
if (opts[i].long_opt && !(opts[i].flags & ARGPARSE_OPT_IGNORE))
writestrings (0, "--", opts[i].long_opt, "\n", NULL);
}
exit (0);
}
if ( i == -2 )
arg->r_opt = ARGPARSE_AMBIGUOUS_OPTION;
else if ( i == -1 )
{
arg->r_opt = ARGPARSE_INVALID_OPTION;
arg->r.ret_str = s+2;
}
else
arg->r_opt = opts[i].short_opt;
if ( i < 0 )
;
else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) )
{
if ( argpos )
{
s2 = argpos+1;
if ( !*s2 )
s2 = NULL;
}
else
s2 = argv[1];
if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
{
arg->r_type = ARGPARSE_TYPE_NONE; /* Argument is optional. */
}
else if ( !s2 )
{
arg->r_opt = ARGPARSE_MISSING_ARG;
}
else if ( !argpos && *s2 == '-'
&& (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
{
/* The argument is optional and the next seems to be an
option. We do not check this possible option but
assume no argument */
arg->r_type = ARGPARSE_TYPE_NONE;
}
else
{
set_opt_arg (arg, opts[i].flags, s2);
if ( !argpos )
{
argc--; argv++; idx++; /* Skip one. */
}
}
}
else
{
/* Does not take an argument. */
if ( argpos )
arg->r_type = ARGPARSE_UNEXPECTED_ARG;
else
arg->r_type = 0;
}
argc--; argv++; idx++; /* Set to next one. */
}
else if ( (*s == '-' && s[1]) || arg->internal.inarg )
{
/* Short option. */
int dash_kludge = 0;
i = 0;
if ( !arg->internal.inarg )
{
arg->internal.inarg++;
if ( (arg->flags & ARGPARSE_FLAG_ONEDASH) )
{
for (i=0; opts[i].short_opt; i++ )
if ( opts[i].long_opt && !strcmp (opts[i].long_opt, s+1))
{
dash_kludge = 1;
break;
}
}
}
s += arg->internal.inarg;
if (!dash_kludge )
{
for (i=0; opts[i].short_opt; i++ )
if ( opts[i].short_opt == *s )
break;
}
if ( !opts[i].short_opt && ( *s == 'h' || *s == '?' ) )
show_help (opts, arg->flags);
arg->r_opt = opts[i].short_opt;
if (!opts[i].short_opt )
{
arg->r_opt = (opts[i].flags & ARGPARSE_OPT_COMMAND)?
ARGPARSE_INVALID_COMMAND:ARGPARSE_INVALID_OPTION;
arg->internal.inarg++; /* Point to the next arg. */
arg->r.ret_str = s;
}
else if ( (opts[i].flags & ARGPARSE_TYPE_MASK) )
{
if ( s[1] && !dash_kludge )
{
s2 = s+1;
set_opt_arg (arg, opts[i].flags, s2);
}
else
{
s2 = argv[1];
if ( !s2 && (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
{
arg->r_type = ARGPARSE_TYPE_NONE;
}
else if ( !s2 )
{
arg->r_opt = ARGPARSE_MISSING_ARG;
}
else if ( *s2 == '-' && s2[1]
&& (opts[i].flags & ARGPARSE_OPT_OPTIONAL) )
{
/* The argument is optional and the next seems to
be an option. We do not check this possible
option but assume no argument. */
arg->r_type = ARGPARSE_TYPE_NONE;
}
else
{
set_opt_arg (arg, opts[i].flags, s2);
argc--; argv++; idx++; /* Skip one. */
}
}
s = "x"; /* This is so that !s[1] yields false. */
}
else
{
/* Does not take an argument. */
arg->r_type = ARGPARSE_TYPE_NONE;
arg->internal.inarg++; /* Point to the next arg. */
}
if ( !s[1] || dash_kludge )
{
/* No more concatenated short options. */
arg->internal.inarg = 0;
argc--; argv++; idx++;
}
}
else if ( arg->flags & ARGPARSE_FLAG_MIXED )
{
arg->r_opt = ARGPARSE_IS_ARG;
arg->r_type = 2;
arg->r.ret_str = s;
argc--; argv++; idx++; /* Set to next one. */
}
else
{
arg->internal.stopped = 1; /* Stop option processing. */
goto next_one;
}
leave:
*arg->argc = argc;
*arg->argv = argv;
arg->internal.idx = idx;
return arg->r_opt;
}
/* Returns: -1 on error, 0 for an integer type and 1 for a non integer
type argument. */
static int
set_opt_arg (ARGPARSE_ARGS *arg, unsigned flags, char *s)
{
int base = (flags & ARGPARSE_OPT_PREFIX)? 0 : 10;
long l;
switch ( (arg->r_type = (flags & ARGPARSE_TYPE_MASK)) )
{
case ARGPARSE_TYPE_LONG:
case ARGPARSE_TYPE_INT:
errno = 0;
l = strtol (s, NULL, base);
if ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE)
{
arg->r_opt = ARGPARSE_INVALID_ARG;
return -1;
}
if (arg->r_type == ARGPARSE_TYPE_LONG)
arg->r.ret_long = l;
else if ( (l < 0 && l < INT_MIN) || l > INT_MAX )
{
arg->r_opt = ARGPARSE_INVALID_ARG;
return -1;
}
else
arg->r.ret_int = (int)l;
return 0;
case ARGPARSE_TYPE_ULONG:
while (isascii (*s) && isspace(*s))
s++;
if (*s == '-')
{
arg->r.ret_ulong = 0;
arg->r_opt = ARGPARSE_INVALID_ARG;
return -1;
}
errno = 0;
arg->r.ret_ulong = strtoul (s, NULL, base);
if (arg->r.ret_ulong == ULONG_MAX && errno == ERANGE)
{
arg->r_opt = ARGPARSE_INVALID_ARG;
return -1;
}
return 0;
case ARGPARSE_TYPE_STRING:
default:
arg->r.ret_str = s;
return 1;
}
}
static size_t
long_opt_strlen( ARGPARSE_OPTS *o )
{
size_t n = strlen (o->long_opt);
if ( o->description && *o->description == '|' )
{
const char *s;
int is_utf8 = is_native_utf8 ();
s=o->description+1;
if ( *s != '=' )
n++;
/* For a (mostly) correct length calculation we exclude
continuation bytes (10xxxxxx) if we are on a native utf8
terminal. */
for (; *s && *s != '|'; s++ )
if ( is_utf8 && (*s&0xc0) != 0x80 )
n++;
}
return n;
}
/****************
* Print formatted help. The description string has some special
* meanings:
* - A description string which is "@" suppresses help output for
* this option
* - a description,ine which starts with a '@' and is followed by
* any other characters is printed as is; this may be used for examples
* and such.
* - A description which starts with a '|' outputs the string between this
* bar and the next one as arguments of the long option.
*/
static void
show_help (ARGPARSE_OPTS *opts, unsigned int flags)
{
const char *s;
char tmp[2];
show_version ();
writestrings (0, "\n", NULL);
s = strusage (42);
if (s && *s == '1')
{
s = strusage (40);
writestrings (1, s, NULL);
if (*s && s[strlen(s)] != '\n')
writestrings (1, "\n", NULL);
}
s = strusage(41);
writestrings (0, s, "\n", NULL);
if ( opts[0].description )
{
/* Auto format the option description. */
int i,j, indent;
/* Get max. length of long options. */
for (i=indent=0; opts[i].short_opt; i++ )
{
if ( opts[i].long_opt )
if ( !opts[i].description || *opts[i].description != '@' )
if ( (j=long_opt_strlen(opts+i)) > indent && j < 35 )
indent = j;
}
/* Example: " -v, --verbose Viele Sachen ausgeben" */
indent += 10;
if ( *opts[0].description != '@' )
writestrings (0, "Options:", "\n", NULL);
for (i=0; opts[i].short_opt; i++ )
{
s = map_static_macro_string (_( opts[i].description ));
if ( s && *s== '@' && !s[1] ) /* Hide this line. */
continue;
if ( s && *s == '@' ) /* Unindented comment only line. */
{
for (s++; *s; s++ )
{
if ( *s == '\n' )
{
if( s[1] )
writestrings (0, "\n", NULL);
}
else
{
tmp[0] = *s;
tmp[1] = 0;
writestrings (0, tmp, NULL);
}
}
writestrings (0, "\n", NULL);
continue;
}
j = 3;
if ( opts[i].short_opt < 256 )
{
tmp[0] = opts[i].short_opt;
tmp[1] = 0;
writestrings (0, " -", tmp, NULL );
if ( !opts[i].long_opt )
{
if (s && *s == '|' )
{
writestrings (0, " ", NULL); j++;
for (s++ ; *s && *s != '|'; s++, j++ )
{
tmp[0] = *s;
tmp[1] = 0;
writestrings (0, tmp, NULL);
}
if ( *s )
s++;
}
}
}
else
writestrings (0, " ", NULL);
if ( opts[i].long_opt )
{
tmp[0] = opts[i].short_opt < 256?',':' ';
tmp[1] = 0;
j += writestrings (0, tmp, " --", opts[i].long_opt, NULL);
if (s && *s == '|' )
{
if ( *++s != '=' )
{
writestrings (0, " ", NULL);
j++;
}
for ( ; *s && *s != '|'; s++, j++ )
{
tmp[0] = *s;
tmp[1] = 0;
writestrings (0, tmp, NULL);
}
if ( *s )
s++;
}
writestrings (0, " ", NULL);
j += 3;
}
for (;j < indent; j++ )
writestrings (0, " ", NULL);
if ( s )
{
if ( *s && j > indent )
{
writestrings (0, "\n", NULL);
for (j=0;j < indent; j++ )
writestrings (0, " ", NULL);
}
for (; *s; s++ )
{
if ( *s == '\n' )
{
if ( s[1] )
{
writestrings (0, "\n", NULL);
for (j=0; j < indent; j++ )
writestrings (0, " ", NULL);
}
}
else
{
tmp[0] = *s;
tmp[1] = 0;
writestrings (0, tmp, NULL);
}
}
}
writestrings (0, "\n", NULL);
}
if ( (flags & ARGPARSE_FLAG_ONEDASH) )
writestrings (0, "\n(A single dash may be used "
"instead of the double ones)\n", NULL);
}
if ( (s=strusage(19)) )
{
writestrings (0, "\n", NULL);
writestrings (0, s, NULL);
}
flushstrings (0);
exit(0);
}
static void
show_version ()
{
const char *s;
int i;
/* Version line. */
writestrings (0, strusage (11), NULL);
if ((s=strusage (12)))
writestrings (0, " (", s, ")", NULL);
writestrings (0, " ", strusage (13), "\n", NULL);
/* Additional version lines. */
for (i=20; i < 30; i++)
if ((s=strusage (i)))
writestrings (0, s, "\n", NULL);
/* Copyright string. */
if ((s=strusage (14)))
writestrings (0, s, "\n", NULL);
/* Licence string. */
if( (s=strusage (10)) )
writestrings (0, s, "\n", NULL);
/* Copying conditions. */
if ( (s=strusage(15)) )
writestrings (0, s, NULL);
/* Thanks. */
if ((s=strusage(18)))
writestrings (0, s, NULL);
/* Additional program info. */
for (i=30; i < 40; i++ )
if ( (s=strusage (i)) )
writestrings (0, s, NULL);
flushstrings (0);
}
void
usage (int level)
{
const char *p;
if (!level)
{
writestrings (1, strusage(11), " ", strusage(13), "; ",
strusage (14), "\n", NULL);
flushstrings (1);
}
else if (level == 1)
{
p = strusage (40);
writestrings (1, p, NULL);
if (*p && p[strlen(p)] != '\n')
writestrings (1, "\n", NULL);
exit (2);
}
else if (level == 2)
{
p = strusage (42);
if (p && *p == '1')
{
p = strusage (40);
writestrings (1, p, NULL);
if (*p && p[strlen(p)] != '\n')
writestrings (1, "\n", NULL);
}
writestrings (0, strusage(41), "\n", NULL);
exit (0);
}
}
/* Level
* 0: Print copyright string to stderr
* 1: Print a short usage hint to stderr and terminate
* 2: Print a long usage hint to stdout and terminate
* 10: Return license info string
* 11: Return the name of the program
* 12: Return optional name of package which includes this program.
* 13: version string
* 14: copyright string
* 15: Short copying conditions (with LFs)
* 16: Long copying conditions (with LFs)
* 17: Optional printable OS name
* 18: Optional thanks list (with LFs)
* 19: Bug report info
*20..29: Additional lib version strings.
*30..39: Additional program info (with LFs)
* 40: short usage note (with LF)
* 41: long usage note (with LF)
* 42: Flag string:
* First char is '1':
* The short usage notes needs to be printed
* before the long usage note.
*/
const char *
strusage( int level )
{
const char *p = strusage_handler? strusage_handler(level) : NULL;
if ( p )
return map_static_macro_string (p);
switch ( level )
{
case 10:
#if ARGPARSE_GPL_VERSION == 3
p = ("License GPLv3+: GNU GPL version 3 or later "
"<https://gnu.org/licenses/gpl.html>");
#else
p = ("License GPLv2+: GNU GPL version 2 or later "
"<https://gnu.org/licenses/>");
#endif
break;
case 11: p = "foo"; break;
case 13: p = "0.0"; break;
case 14: p = ARGPARSE_CRIGHT_STR; break;
case 15: p =
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n";
break;
case 16: p =
"This is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version "
ARGPARSE_STR2(ARGPARSE_GPL_VERSION)
" of the License, or\n"
"(at your option) any later version.\n\n"
"It is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n\n"
"You should have received a copy of the GNU General Public License\n"
"along with this software. If not, see <https://gnu.org/licenses/>.\n";
break;
case 40: /* short and long usage */
case 41: p = ""; break;
}
return p;
}
/* Set the usage handler. This function is basically a constructor. */
void
set_strusage ( const char *(*f)( int ) )
{
strusage_handler = f;
}
#ifdef TEST
static struct {
int verbose;
int debug;
char *outfile;
char *crf;
int myopt;
int echo;
int a_long_one;
} opt;
int
main(int argc, char **argv)
{
ARGPARSE_OPTS opts[] = {
ARGPARSE_x('v', "verbose", NONE, 0, "Laut sein"),
ARGPARSE_s_n('e', "echo" , ("Zeile ausgeben, damit wir sehen, "
"was wir eingegeben haben")),
ARGPARSE_s_n('d', "debug", "Debug\nfalls mal etwas\nschief geht"),
ARGPARSE_s_s('o', "output", 0 ),
ARGPARSE_o_s('c', "cross-ref", "cross-reference erzeugen\n" ),
/* Note that on a non-utf8 terminal the ß might garble the output. */
ARGPARSE_s_n('s', "street","|Straße|set the name of the street to Straße"),
ARGPARSE_o_i('m', "my-option", 0),
ARGPARSE_s_n(500, "a-long-option", 0 ),
ARGPARSE_end()
};
ARGPARSE_ARGS pargs = { &argc, &argv, (ARGPARSE_FLAG_ALL
| ARGPARSE_FLAG_MIXED
| ARGPARSE_FLAG_ONEDASH) };
int i;
while (arg_parse (&pargs, opts))
{
switch (pargs.r_opt)
{
case ARGPARSE_IS_ARG :
printf ("arg='%s'\n", pargs.r.ret_str);
break;
case 'v': opt.verbose++; break;
case 'e': opt.echo++; break;
case 'd': opt.debug++; break;
case 'o': opt.outfile = pargs.r.ret_str; break;
case 'c': opt.crf = pargs.r_type? pargs.r.ret_str:"a.crf"; break;
case 'm': opt.myopt = pargs.r_type? pargs.r.ret_int : 1; break;
case 500: opt.a_long_one++; break;
default : pargs.err = ARGPARSE_PRINT_WARNING; break;
}
}
for (i=0; i < argc; i++ )
printf ("%3d -> (%s)\n", i, argv[i] );
puts ("Options:");
if (opt.verbose)
printf (" verbose=%d\n", opt.verbose );
if (opt.debug)
printf (" debug=%d\n", opt.debug );
if (opt.outfile)
printf (" outfile='%s'\n", opt.outfile );
if (opt.crf)
printf (" crffile='%s'\n", opt.crf );
if (opt.myopt)
printf (" myopt=%d\n", opt.myopt );
if (opt.a_long_one)
printf (" a-long-one=%d\n", opt.a_long_one );
if (opt.echo)
printf (" echo=%d\n", opt.echo );
return 0;
}
#endif /*TEST*/
/**** bottom of file ****/
diff --git a/common/asshelp.c b/common/asshelp.c
index 83c378786..fe49aa83a 100644
--- a/common/asshelp.c
+++ b/common/asshelp.c
@@ -1,698 +1,698 @@
/* asshelp.c - Helper functions for Assuan
* Copyright (C) 2002, 2004, 2007, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "i18n.h"
#include "util.h"
#include "exechelp.h"
#include "sysutils.h"
#include "status.h"
#include "membuf.h"
#include "asshelp.h"
/* The type we use for lock_agent_spawning. */
#ifdef HAVE_W32_SYSTEM
# define lock_spawn_t HANDLE
#else
# define lock_spawn_t dotlock_t
#endif
/* The time we wait until the agent or the dirmngr are ready for
operation after we started them before giving up. */
#ifdef HAVE_W32CE_SYSTEM
# define SECS_TO_WAIT_FOR_AGENT 30
# define SECS_TO_WAIT_FOR_KEYBOXD 30
# define SECS_TO_WAIT_FOR_DIRMNGR 30
#else
# define SECS_TO_WAIT_FOR_AGENT 5
# define SECS_TO_WAIT_FOR_KEYBOXD 5
# define SECS_TO_WAIT_FOR_DIRMNGR 5
#endif
/* A bitfield that specifies the assuan categories to log. This is
identical to the default log handler of libassuan. We need to do
it ourselves because we use a custom log handler and want to use
the same assuan variables to select the categories to log. */
static int log_cats;
#define TEST_LOG_CAT(x) (!! (log_cats & (1 << (x - 1))))
/* The assuan log monitor used to temporary inhibit log messages from
* assuan. */
static int (*my_log_monitor) (assuan_context_t ctx,
unsigned int cat,
const char *msg);
static int
my_libassuan_log_handler (assuan_context_t ctx, void *hook,
unsigned int cat, const char *msg)
{
unsigned int dbgval;
if (! TEST_LOG_CAT (cat))
return 0;
dbgval = hook? *(unsigned int*)hook : 0;
if (!(dbgval & 1024))
return 0; /* Assuan debugging is not enabled. */
if (ctx && my_log_monitor && !my_log_monitor (ctx, cat, msg))
return 0; /* Temporary disabled. */
if (msg)
log_string (GPGRT_LOGLVL_DEBUG, msg);
return 1;
}
/* Setup libassuan to use our own logging functions. Should be used
early at startup. */
void
setup_libassuan_logging (unsigned int *debug_var_address,
int (*log_monitor)(assuan_context_t ctx,
unsigned int cat,
const char *msg))
{
char *flagstr;
flagstr = getenv ("ASSUAN_DEBUG");
if (flagstr)
log_cats = atoi (flagstr);
else /* Default to log the control channel. */
log_cats = (1 << (ASSUAN_LOG_CONTROL - 1));
my_log_monitor = log_monitor;
assuan_set_log_cb (my_libassuan_log_handler, debug_var_address);
}
/* Change the Libassuan log categories to those given by NEWCATS.
NEWCATS is 0 the default category of ASSUAN_LOG_CONTROL is
selected. Note, that setup_libassuan_logging overrides the values
given here. */
void
set_libassuan_log_cats (unsigned int newcats)
{
if (newcats)
log_cats = newcats;
else /* Default to log the control channel. */
log_cats = (1 << (ASSUAN_LOG_CONTROL - 1));
}
static gpg_error_t
send_one_option (assuan_context_t ctx, gpg_err_source_t errsource,
const char *name, const char *value, int use_putenv)
{
gpg_error_t err;
char *optstr;
(void)errsource;
if (!value || !*value)
err = 0; /* Avoid sending empty strings. */
else if (asprintf (&optstr, "OPTION %s%s=%s",
use_putenv? "putenv=":"", name, value) < 0)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL);
xfree (optstr);
}
return err;
}
/* Send the assuan commands pertaining to the pinentry environment. The
OPT_* arguments are optional and may be used to override the
defaults taken from the current locale. */
gpg_error_t
send_pinentry_environment (assuan_context_t ctx,
gpg_err_source_t errsource,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env)
{
gpg_error_t err = 0;
#if defined(HAVE_SETLOCALE)
char *old_lc = NULL;
#endif
char *dft_lc = NULL;
const char *dft_ttyname;
int iterator;
const char *name, *assname, *value;
int is_default;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, &assname)))
{
value = session_env_getenv_or_default (session_env, name, NULL);
if (!value)
continue;
if (assname)
err = send_one_option (ctx, errsource, assname, value, 0);
else
{
err = send_one_option (ctx, errsource, name, value, 1);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* Server too old; can't pass the new envvars. */
}
if (err)
return err;
}
dft_ttyname = session_env_getenv_or_default (session_env, "GPG_TTY",
&is_default);
if (dft_ttyname && !is_default)
dft_ttyname = NULL; /* We need the default value. */
/* Send the value for LC_CTYPE. */
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
old_lc = setlocale (LC_CTYPE, NULL);
if (old_lc)
{
old_lc = xtrystrdup (old_lc);
if (!old_lc)
return gpg_error_from_syserror ();
}
dft_lc = setlocale (LC_CTYPE, "");
#endif
if (opt_lc_ctype || (dft_ttyname && dft_lc))
{
err = send_one_option (ctx, errsource, "lc-ctype",
opt_lc_ctype ? opt_lc_ctype : dft_lc, 0);
}
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
if (old_lc)
{
setlocale (LC_CTYPE, old_lc);
xfree (old_lc);
}
#endif
if (err)
return err;
/* Send the value for LC_MESSAGES. */
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
old_lc = setlocale (LC_MESSAGES, NULL);
if (old_lc)
{
old_lc = xtrystrdup (old_lc);
if (!old_lc)
return gpg_error_from_syserror ();
}
dft_lc = setlocale (LC_MESSAGES, "");
#endif
if (opt_lc_messages || (dft_ttyname && dft_lc))
{
err = send_one_option (ctx, errsource, "lc-messages",
opt_lc_messages ? opt_lc_messages : dft_lc, 0);
}
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
if (old_lc)
{
setlocale (LC_MESSAGES, old_lc);
xfree (old_lc);
}
#endif
if (err)
return err;
return 0;
}
/* Lock a spawning process. The caller needs to provide the address
of a variable to store the lock information and the name or the
process. */
static gpg_error_t
lock_spawning (lock_spawn_t *lock, const char *homedir, const char *name,
int verbose)
{
char *fname;
(void)verbose;
*lock = NULL;
fname = make_absfilename_try
(homedir,
!strcmp (name, "agent")? "gnupg_spawn_agent_sentinel":
!strcmp (name, "dirmngr")? "gnupg_spawn_dirmngr_sentinel":
/* */ "gnupg_spawn_unknown_sentinel",
NULL);
if (!fname)
return gpg_error_from_syserror ();
*lock = dotlock_create (fname, 0);
xfree (fname);
if (!*lock)
return gpg_error_from_syserror ();
/* FIXME: We should use a timeout of 5000 here - however
make_dotlock does not yet support values other than -1 and 0. */
if (dotlock_take (*lock, -1))
return gpg_error_from_syserror ();
return 0;
}
/* Unlock the spawning process. */
static void
unlock_spawning (lock_spawn_t *lock, const char *name)
{
if (*lock)
{
(void)name;
dotlock_destroy (*lock);
*lock = NULL;
}
}
/* Helper to start a service. SECS gives the number of seconds to
* wait. SOCKNAME is the name of the socket to connect. VERBOSE is
* the usual verbose flag. CTX is the assuan context. CONNECT_FLAGS
* are the assuan connect flags. DID_SUCCESS_MSG will be set to 1 if
* a success messages has been printed.
*/
static gpg_error_t
wait_for_sock (int secs, int module_name_id, const char *sockname,
unsigned int connect_flags,
int verbose, assuan_context_t ctx, int *did_success_msg)
{
gpg_error_t err = 0;
int target_us = secs * 1000000;
int elapsed_us = 0;
/*
* 977us * 1024 = just a little more than 1s.
* so we will double this timeout 10 times in the first
* second, and then switch over to 1s checkins.
*/
int next_sleep_us = 977;
int lastalert = secs+1;
int secsleft;
while (elapsed_us < target_us)
{
if (verbose)
{
secsleft = (target_us - elapsed_us + 999999)/1000000;
/* log_clock ("left=%d last=%d targ=%d elap=%d next=%d\n", */
/* secsleft, lastalert, target_us, elapsed_us, */
/* next_sleep_us); */
if (secsleft < lastalert)
{
log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("waiting for the dirmngr to come up ... (%ds)\n"):
module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
_("waiting for the keyboxd to come up ... (%ds)\n"):
_("waiting for the agent to come up ... (%ds)\n"),
secsleft);
lastalert = secsleft;
}
}
gnupg_usleep (next_sleep_us);
elapsed_us += next_sleep_us;
err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (!err)
{
if (verbose)
{
log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("connection to the dirmngr established\n"):
module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
_("connection to the keyboxd established\n"):
_("connection to the agent established\n"));
*did_success_msg = 1;
}
break;
}
next_sleep_us *= 2;
if (next_sleep_us > 1000000)
next_sleep_us = 1000000;
}
return err;
}
/* Try to connect to a new service via socket or start it if it is not
* running and AUTOSTART is set. Handle the server's initial
* greeting. Returns a new assuan context at R_CTX or an error code.
* MODULE_NAME_ID is one of:
* GNUPG_MODULE_NAME_AGENT
* GNUPG_MODULE_NAME_DIRMNGR
*/
static gpg_error_t
start_new_service (assuan_context_t *r_ctx,
int module_name_id,
gpg_err_source_t errsource,
const char *program_name,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env,
int autostart, int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
gpg_error_t err;
assuan_context_t ctx;
int did_success_msg = 0;
char *sockname;
const char *printed_name;
const char *lock_name;
const char *status_start_line;
int no_service_err;
int seconds_to_wait;
unsigned int connect_flags = 0;
const char *argv[6];
*r_ctx = NULL;
err = assuan_new (&ctx);
if (err)
{
log_error ("error allocating assuan context: %s\n", gpg_strerror (err));
return err;
}
switch (module_name_id)
{
case GNUPG_MODULE_NAME_AGENT:
sockname = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
lock_name = "agent";
printed_name = "gpg-agent";
status_start_line = "starting_agent ? 0 0";
no_service_err = GPG_ERR_NO_AGENT;
seconds_to_wait = SECS_TO_WAIT_FOR_AGENT;
break;
case GNUPG_MODULE_NAME_DIRMNGR:
sockname = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL);
lock_name = "dirmngr";
printed_name = "dirmngr";
status_start_line = "starting_dirmngr ? 0 0";
no_service_err = GPG_ERR_NO_DIRMNGR;
seconds_to_wait = SECS_TO_WAIT_FOR_DIRMNGR;
break;
case GNUPG_MODULE_NAME_KEYBOXD:
sockname = make_filename (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
lock_name = "keyboxd";
printed_name = "keyboxd";
status_start_line = "starting_keyboxd ? 0 0";
no_service_err = GPG_ERR_NO_KEYBOXD;
seconds_to_wait = SECS_TO_WAIT_FOR_KEYBOXD;
connect_flags |= ASSUAN_SOCKET_CONNECT_FDPASSING;
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
assuan_release (ctx);
return err;
}
err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (err && autostart)
{
char *abs_homedir;
lock_spawn_t lock;
char *program = NULL;
const char *program_arg = NULL;
char *p;
const char *s;
int i;
/* With no success start a new server. */
if (!program_name || !*program_name)
program_name = gnupg_module_name (module_name_id);
else if ((s=strchr (program_name, '|')) && s[1] == '-' && s[2]=='-')
{
/* Hack to insert an additional option on the command line. */
program = xtrystrdup (program_name);
if (!program)
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
xfree (sockname);
assuan_release (ctx);
return tmperr;
}
p = strchr (program, '|');
*p++ = 0;
program_arg = p;
}
if (verbose)
log_info (_("no running %s - starting '%s'\n"),
printed_name, program_name);
if (status_cb)
status_cb (status_cb_arg, STATUS_PROGRESS, status_start_line, NULL);
/* We better pass an absolute home directory to the service just
* in case the service does not convert the passed name to an
* absolute one (which it should do). */
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
log_error ("error building filename: %s\n", gpg_strerror (tmperr));
xfree (sockname);
assuan_release (ctx);
xfree (program);
return tmperr;
}
if (fflush (NULL))
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
log_error ("error flushing pending output: %s\n", strerror (errno));
xfree (sockname);
assuan_release (ctx);
xfree (abs_homedir);
xfree (program);
return tmperr;
}
i = 0;
argv[i++] = "--homedir";
argv[i++] = abs_homedir;
if (module_name_id == GNUPG_MODULE_NAME_AGENT)
argv[i++] = "--use-standard-socket";
if (program_arg)
argv[i++] = program_arg;
argv[i++] = "--daemon";
argv[i++] = NULL;
if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose))
&& assuan_socket_connect (ctx, sockname, 0, connect_flags))
{
#ifdef HAVE_W32_SYSTEM
err = gnupg_spawn_process_detached (program? program : program_name,
argv, NULL);
#else /*!W32*/
pid_t pid;
err = gnupg_spawn_process_fd (program? program : program_name,
argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (program? program : program_name,
pid, 1, NULL);
#endif /*!W32*/
if (err)
log_error ("failed to start %s '%s': %s\n",
printed_name, program? program : program_name,
gpg_strerror (err));
else
err = wait_for_sock (seconds_to_wait, module_name_id,
sockname, connect_flags,
verbose, ctx, &did_success_msg);
}
unlock_spawning (&lock, lock_name);
xfree (abs_homedir);
xfree (program);
}
xfree (sockname);
if (err)
{
if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED)
log_error ("can't connect to the %s: %s\n",
printed_name, gpg_strerror (err));
assuan_release (ctx);
return gpg_err_make (errsource, no_service_err);
}
if (debug && !did_success_msg)
log_debug ("connection to the %s established\n", printed_name);
if (module_name_id == GNUPG_MODULE_NAME_AGENT)
err = assuan_transact (ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (!err
&& module_name_id == GNUPG_MODULE_NAME_AGENT)
{
err = send_pinentry_environment (ctx, errsource,
opt_lc_ctype, opt_lc_messages,
session_env);
if (gpg_err_code (err) == GPG_ERR_FORBIDDEN
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
{
/* Check whether the agent is in restricted mode. */
if (!assuan_transact (ctx, "GETINFO restricted",
NULL, NULL, NULL, NULL, NULL, NULL))
{
if (verbose)
log_info (_("connection to the agent is in restricted mode\n"));
err = 0;
}
}
}
if (err)
{
assuan_release (ctx);
return err;
}
*r_ctx = ctx;
return 0;
}
-/* Try to connect tothe agent or start a new one. */
+/* Try to connect to the agent or start a new one. */
gpg_error_t
start_new_gpg_agent (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *agent_program,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env,
int autostart, int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
return start_new_service (r_ctx, GNUPG_MODULE_NAME_AGENT,
errsource, agent_program,
opt_lc_ctype, opt_lc_messages, session_env,
autostart, verbose, debug,
status_cb, status_cb_arg);
}
/* Try to connect to the dirmngr via a socket. On platforms
supporting it, start it up if needed and if AUTOSTART is true.
Returns a new assuan context at R_CTX or an error code. */
gpg_error_t
start_new_keyboxd (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *keyboxd_program,
int autostart, int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
return start_new_service (r_ctx, GNUPG_MODULE_NAME_KEYBOXD,
errsource, keyboxd_program,
NULL, NULL, NULL,
autostart, verbose, debug,
status_cb, status_cb_arg);
}
/* Try to connect to the dirmngr via a socket. On platforms
supporting it, start it up if needed and if AUTOSTART is true.
Returns a new assuan context at R_CTX or an error code. */
gpg_error_t
start_new_dirmngr (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *dirmngr_program,
int autostart, int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
#ifndef USE_DIRMNGR_AUTO_START
autostart = 0;
#endif
return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR,
errsource, dirmngr_program,
NULL, NULL, NULL,
autostart, verbose, debug,
status_cb, status_cb_arg);
}
/* Return the version of a server using "GETINFO version". On success
0 is returned and R_VERSION receives a malloced string with the
version which must be freed by the caller. On error NULL is stored
at R_VERSION and an error code returned. Mode is in general 0 but
certain values may be used to modify the used version command:
MODE == 0 = Use "GETINFO version"
MODE == 2 - Use "SCD GETINFO version"
*/
gpg_error_t
get_assuan_server_version (assuan_context_t ctx, int mode, char **r_version)
{
gpg_error_t err;
membuf_t data;
init_membuf (&data, 64);
err = assuan_transact (ctx,
mode == 2? "SCD GETINFO version"
/**/ : "GETINFO version",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, NULL));
*r_version = NULL;
}
else
{
put_membuf (&data, "", 1);
*r_version = get_membuf (&data, NULL);
if (!*r_version)
err = gpg_error_from_syserror ();
}
return err;
}
diff --git a/common/asshelp2.c b/common/asshelp2.c
index 8410808e3..a62189df9 100644
--- a/common/asshelp2.c
+++ b/common/asshelp2.c
@@ -1,192 +1,192 @@
/* asshelp2.c - More helper functions for Assuan
* Copyright (C) 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assuan.h>
#include "util.h"
#include "asshelp.h"
#include "status.h"
/* A variable with a function to be used to return the current assuan
* context for a CTRL variable. This needs to be set using the
* set_assuan_ctx_func function. */
static assuan_context_t (*the_assuan_ctx_func)(ctrl_t ctrl);
-/* Set FUNC to be used as a mapping fucntion from CTRL to an assuan
+/* Set FUNC to be used as a mapping function from CTRL to an assuan
* context. Pass NULL for FUNC to disable the use of the assuan
* context in this module. */
void
set_assuan_context_func (assuan_context_t (*func)(ctrl_t ctrl))
{
the_assuan_ctx_func = func;
}
/* Helper function to print an assuan status line using a printf
format string. */
gpg_error_t
vprint_assuan_status (assuan_context_t ctx,
const char *keyword,
const char *format, va_list arg_ptr)
{
int rc;
char *buf;
rc = gpgrt_vasprintf (&buf, format, arg_ptr);
if (rc < 0)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
rc = assuan_write_status (ctx, keyword, buf);
xfree (buf);
return rc;
}
/* Helper function to print an assuan status line using a printf
format string. */
gpg_error_t
print_assuan_status (assuan_context_t ctx,
const char *keyword,
const char *format, ...)
{
va_list arg_ptr;
gpg_error_t err;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper function to print a list of strings as an assuan status
* line. KEYWORD is the first item on the status line. ARG_PTR is a
* list of strings which are all separated by a space in the output.
* The last argument must be a NULL. Linefeeds and carriage returns
* characters (which are not allowed in an Assuan status line) are
* silently quoted in C-style. */
gpg_error_t
vprint_assuan_status_strings (assuan_context_t ctx,
const char *keyword, va_list arg_ptr)
{
gpg_error_t err = 0;
const char *text;
char buf[950], *p;
size_t n;
p = buf;
n = 0;
while ((text = va_arg (arg_ptr, const char *)) && n < DIM (buf)-3 )
{
if (n)
{
*p++ = ' ';
n++;
}
for ( ; *text && n < DIM (buf)-3; n++, text++)
{
if (*text == '\n')
{
*p++ = '\\';
*p++ = 'n';
n++;
}
else if (*text == '\r')
{
*p++ = '\\';
*p++ = 'r';
n++;
}
else
*p++ = *text;
}
}
*p = 0;
err = assuan_write_status (ctx, keyword, buf);
return err;
}
/* See vprint_assuan_status_strings. */
gpg_error_t
print_assuan_status_strings (assuan_context_t ctx, const char *keyword, ...)
{
va_list arg_ptr;
gpg_error_t err;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
* arg instead of an assuan context as first argument. */
gpg_error_t
status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !the_assuan_ctx_func || !(ctx = the_assuan_ctx_func (ctrl)))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Same as sytus_printf but takes a status number instead of a
* keyword. */
gpg_error_t
status_no_printf (ctrl_t ctrl, int no, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !the_assuan_ctx_func || !(ctx = the_assuan_ctx_func (ctrl)))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, get_status_string (no), format, arg_ptr);
va_end (arg_ptr);
return err;
}
diff --git a/common/audit.c b/common/audit.c
index 179bf72fe..6185df37c 100644
--- a/common/audit.c
+++ b/common/audit.c
@@ -1,1323 +1,1323 @@
/* audit.c - GnuPG's audit subsystem
* Copyright (C) 2007, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "util.h"
#include "i18n.h"
#include "audit.h"
#include "audit-events.h"
/* A list to maintain a list of helptags. */
struct helptag_s
{
struct helptag_s *next;
const char *name;
};
typedef struct helptag_s *helptag_t;
/* One log entry. */
struct log_item_s
{
audit_event_t event; /* The event. */
gpg_error_t err; /* The logged error code. */
int intvalue; /* A logged integer value. */
char *string; /* A malloced string or NULL. */
ksba_cert_t cert; /* A certifciate or NULL. */
int have_err:1;
int have_intvalue:1;
};
typedef struct log_item_s *log_item_t;
/* The main audit object. */
struct audit_ctx_s
{
const char *failure; /* If set a description of the internal failure. */
audit_type_t type;
log_item_t log; /* The table with the log entries. */
size_t logsize; /* The allocated size for LOG. */
size_t logused; /* The used size of LOG. */
estream_t outstream; /* The current output stream. */
int use_html; /* The output shall be HTML formatted. */
int indentlevel; /* Current level of indentation. */
helptag_t helptags; /* List of help keys. */
};
static void writeout_para (audit_ctx_t ctx,
const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
static void writeout_li (audit_ctx_t ctx, const char *oktext,
const char *format, ...) GPGRT_ATTR_PRINTF(3,4);
static void writeout_rem (audit_ctx_t ctx,
const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
/* Add NAME to the list of help tags. NAME needs to be a const string
- an this function merly stores this pointer. */
+ an this function merely stores this pointer. */
static void
add_helptag (audit_ctx_t ctx, const char *name)
{
helptag_t item;
for (item=ctx->helptags; item; item = item->next)
if (!strcmp (item->name, name))
return; /* Already in the list. */
item = xtrycalloc (1, sizeof *item);
if (!item)
return; /* Don't care about memory problems. */
item->name = name;
item->next = ctx->helptags;
ctx->helptags = item;
}
/* Remove all help tags from the context. */
static void
clear_helptags (audit_ctx_t ctx)
{
while (ctx->helptags)
{
helptag_t tmp = ctx->helptags->next;
xfree (ctx->helptags);
ctx->helptags = tmp;
}
}
static const char *
event2str (audit_event_t event)
{
/* We need the cast so that compiler does not complain about an
always true comparison (>= 0) for an unsigned value. */
int idx = eventstr_msgidxof ((int)event);
if (idx == -1)
return "Unknown event";
else
return eventstr_msgstr + eventstr_msgidx[idx];
}
/* Create a new audit context. In case of an error NULL is returned
and errno set appropriately. */
audit_ctx_t
audit_new (void)
{
audit_ctx_t ctx;
ctx = xtrycalloc (1, sizeof *ctx);
return ctx;
}
/* Release an audit context. Passing NULL for CTX is allowed and does
nothing. */
void
audit_release (audit_ctx_t ctx)
{
int idx;
if (!ctx)
return;
if (ctx->log)
{
for (idx=0; idx < ctx->logused; idx++)
{
if (ctx->log[idx].string)
xfree (ctx->log[idx].string);
if (ctx->log[idx].cert)
ksba_cert_release (ctx->log[idx].cert);
}
xfree (ctx->log);
}
clear_helptags (ctx);
xfree (ctx);
}
/* Set the type for the audit operation. If CTX is NULL, this is a
dummy function. */
void
audit_set_type (audit_ctx_t ctx, audit_type_t type)
{
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (ctx->type && ctx->type != type)
{
ctx->failure = "conflict in type initialization";
return;
}
ctx->type = type;
}
/* Create a new log item and put it into the table. Return that log
item on success; return NULL on memory failure and mark that in
CTX. */
static log_item_t
create_log_item (audit_ctx_t ctx)
{
log_item_t item, table;
size_t size;
if (!ctx->log)
{
size = 10;
table = xtrymalloc (size * sizeof *table);
if (!table)
{
ctx->failure = "Out of memory in create_log_item";
return NULL;
}
ctx->log = table;
ctx->logsize = size;
item = ctx->log + 0;
ctx->logused = 1;
}
else if (ctx->logused >= ctx->logsize)
{
size = ctx->logsize + 10;
table = xtryrealloc (ctx->log, size * sizeof *table);
if (!table)
{
ctx->failure = "Out of memory while reallocating in create_log_item";
return NULL;
}
ctx->log = table;
ctx->logsize = size;
item = ctx->log + ctx->logused++;
}
else
item = ctx->log + ctx->logused++;
item->event = AUDIT_NULL_EVENT;
item->err = 0;
item->have_err = 0;
item->intvalue = 0;
item->have_intvalue = 0;
item->string = NULL;
item->cert = NULL;
return item;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. */
void
audit_log (audit_ctx_t ctx, audit_event_t event)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also adds the result of the operation
to the log. */
void
audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_ok";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->err = err;
item->have_err = 1;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also add the integer VALUE to the log. */
void
audit_log_i (audit_ctx_t ctx, audit_event_t event, int value)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_i";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->intvalue = value;
item->have_intvalue = 1;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also add the integer VALUE to the log. */
void
audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value)
{
log_item_t item;
char *tmp;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_s";
return;
}
tmp = xtrystrdup (value? value : "");
if (!tmp)
{
ctx->failure = "Out of memory in audit_event";
return;
}
if (!(item = create_log_item (ctx)))
{
xfree (tmp);
return;
}
item->event = event;
item->string = tmp;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also adds the certificate CERT and the
result of an operation to the log. */
void
audit_log_cert (audit_ctx_t ctx, audit_event_t event,
ksba_cert_t cert, gpg_error_t err)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_cert";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->err = err;
item->have_err = 1;
if (cert)
{
ksba_cert_ref (cert);
item->cert = cert;
}
}
/* Write TEXT to the outstream. */
static void
writeout (audit_ctx_t ctx, const char *text)
{
if (ctx->use_html)
{
for (; *text; text++)
{
if (*text == '<')
es_fputs ("&lt;", ctx->outstream);
else if (*text == '&')
es_fputs ("&amp;", ctx->outstream);
else
es_putc (*text, ctx->outstream);
}
}
else
es_fputs (text, ctx->outstream);
}
/* Write TEXT to the outstream using a variable argument list. */
static void
writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr)
{
char *buf;
gpgrt_vasprintf (&buf, format, arg_ptr);
if (buf)
{
writeout (ctx, buf);
xfree (buf);
}
else
writeout (ctx, "[!!Out of core!!]");
}
/* Write TEXT as a paragraph. */
static void
writeout_para (audit_ctx_t ctx, const char *format, ...)
{
va_list arg_ptr;
if (ctx->use_html)
es_fputs ("<p>", ctx->outstream);
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
if (ctx->use_html)
es_fputs ("</p>\n", ctx->outstream);
else
es_fputc ('\n', ctx->outstream);
}
static void
enter_li (audit_ctx_t ctx)
{
if (ctx->use_html)
{
if (!ctx->indentlevel)
{
es_fputs ("<table border=\"0\">\n"
" <colgroup>\n"
" <col width=\"80%\" />\n"
" <col width=\"20%\" />\n"
" </colgroup>\n",
ctx->outstream);
}
}
ctx->indentlevel++;
}
static void
leave_li (audit_ctx_t ctx)
{
ctx->indentlevel--;
if (ctx->use_html)
{
if (!ctx->indentlevel)
es_fputs ("</table>\n", ctx->outstream);
}
}
/* Write TEXT as a list element. If OKTEXT is not NULL, append it to
the last line. */
static void
writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...)
{
va_list arg_ptr;
const char *color = NULL;
if (ctx->use_html && format && oktext)
{
if (!strcmp (oktext, "Yes")
|| !strcmp (oktext, "good") )
color = "green";
else if (!strcmp (oktext, "No")
|| !strcmp (oktext, "bad") )
color = "red";
}
if (format && oktext)
{
const char *s = NULL;
if (!strcmp (oktext, "Yes"))
oktext = _("Yes");
else if (!strcmp (oktext, "No"))
oktext = _("No");
else if (!strcmp (oktext, "good"))
{
/* TRANSLATORS: Copy the prefix between the vertical bars
verbatim. It will not be printed. */
oktext = _("|audit-log-result|Good");
}
else if (!strcmp (oktext, "bad"))
oktext = _("|audit-log-result|Bad");
else if (!strcmp (oktext, "unsupported"))
oktext = _("|audit-log-result|Not supported");
else if (!strcmp (oktext, "no-cert"))
oktext = _("|audit-log-result|No certificate");
else if (!strcmp (oktext, "disabled"))
oktext = _("|audit-log-result|Not enabled");
else if (!strcmp (oktext, "error"))
oktext = _("|audit-log-result|Error");
else if (!strcmp (oktext, "not-used"))
oktext = _("|audit-log-result|Not used");
else if (!strcmp (oktext, "okay"))
oktext = _("|audit-log-result|Okay");
else if (!strcmp (oktext, "skipped"))
oktext = _("|audit-log-result|Skipped");
else if (!strcmp (oktext, "some"))
oktext = _("|audit-log-result|Some");
else
s = "";
/* If we have set a prefix, skip it. */
if (!s && *oktext == '|' && (s=strchr (oktext+1,'|')))
oktext = s+1;
}
if (ctx->use_html)
{
int i;
es_fputs (" <tr><td><table><tr><td>", ctx->outstream);
if (color)
es_fprintf (ctx->outstream, "<font color=\"%s\">*</font>", color);
else
es_fputs ("*", ctx->outstream);
for (i=1; i < ctx->indentlevel; i++)
es_fputs ("&nbsp;&nbsp;", ctx->outstream);
es_fputs ("</td><td>", ctx->outstream);
}
else
es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, "");
if (format)
{
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
}
if (ctx->use_html)
es_fputs ("</td></tr></table>", ctx->outstream);
if (format && oktext)
{
if (ctx->use_html)
{
es_fputs ("</td><td>", ctx->outstream);
if (color)
es_fprintf (ctx->outstream, "<font color=\"%s\">", color);
}
else
writeout (ctx, ": ");
writeout (ctx, oktext);
if (color)
es_fputs ("</font>", ctx->outstream);
}
if (ctx->use_html)
es_fputs ("</td></tr>\n", ctx->outstream);
else
es_fputc ('\n', ctx->outstream);
}
/* Write a remark line. */
static void
writeout_rem (audit_ctx_t ctx, const char *format, ...)
{
va_list arg_ptr;
if (ctx->use_html)
{
int i;
es_fputs (" <tr><td><table><tr><td>*", ctx->outstream);
for (i=1; i < ctx->indentlevel; i++)
es_fputs ("&nbsp;&nbsp;", ctx->outstream);
es_fputs ("&nbsp;&nbsp;&nbsp;</td><td> (", ctx->outstream);
}
else
es_fprintf (ctx->outstream, "* %*s (", (ctx->indentlevel-1)*2, "");
if (format)
{
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
}
if (ctx->use_html)
es_fputs (")</td></tr></table></td></tr>\n", ctx->outstream);
else
es_fputs (")\n", ctx->outstream);
}
/* Return the first log item for EVENT. If STOPEVENT is not 0 never
look behind that event in the log. If STARTITEM is not NULL start
search _after_that item. */
static log_item_t
find_next_log_item (audit_ctx_t ctx, log_item_t startitem,
audit_event_t event, audit_event_t stopevent)
{
int idx;
for (idx=0; idx < ctx->logused; idx++)
{
if (startitem)
{
if (ctx->log + idx == startitem)
startitem = NULL;
}
else if (stopevent && ctx->log[idx].event == stopevent)
break;
else if (ctx->log[idx].event == event)
return ctx->log + idx;
}
return NULL;
}
static log_item_t
find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent)
{
return find_next_log_item (ctx, NULL, event, stopevent);
}
/* Helper to a format a serial number. */
static char *
format_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
return NULL;
if (*p != '(')
BUG (); /* Not a valid S-expression. */
n = strtoul (p+1, &endp, 10);
p = endp;
if (*p != ':')
BUG (); /* Not a valid S-expression. */
return bin2hex (p+1, n, NULL);
}
/* Return a malloced string with the serial number and the issuer DN
of the certificate. */
static char *
get_cert_name (ksba_cert_t cert)
{
char *result;
ksba_sexp_t sn;
char *issuer, *p;
if (!cert)
return xtrystrdup ("[no certificate]");
issuer = ksba_cert_get_issuer (cert, 0);
sn = ksba_cert_get_serial (cert);
if (issuer && sn)
{
p = format_serial (sn);
if (!p)
result = xtrystrdup ("[invalid S/N]");
else
{
result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1);
if (result)
{
*result = '#';
strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer);
}
xfree (p);
}
}
else
result = xtrystrdup ("[missing S/N or issuer]");
ksba_free (sn);
xfree (issuer);
return result;
}
/* Return a malloced string with the serial number and the issuer DN
of the certificate. */
static char *
get_cert_subject (ksba_cert_t cert, int idx)
{
char *result;
char *subject;
if (!cert)
return xtrystrdup ("[no certificate]");
subject = ksba_cert_get_subject (cert, idx);
if (subject)
{
result = xtrymalloc (strlen (subject) + 1 + 1);
if (result)
{
*result = '/';
strcpy (result+1, subject);
}
}
else
result = NULL;
xfree (subject);
return result;
}
/* List the given certificiate. If CERT is NULL, this is a NOP. */
static void
list_cert (audit_ctx_t ctx, ksba_cert_t cert, int with_subj)
{
char *name;
int idx;
name = get_cert_name (cert);
writeout_rem (ctx, "%s", name);
xfree (name);
if (with_subj)
{
enter_li (ctx);
for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
/* List the chain of certificates from STARTITEM up to STOPEVENT. The
certificates are written out as comments. */
static void
list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent)
{
log_item_t item;
startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent);
writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available"));
if (!startitem)
return;
item = find_next_log_item (ctx, startitem,
AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
if (!item)
writeout_rem (ctx, "%s", _("root certificate missing"));
else
{
list_cert (ctx, item->cert, 0);
}
item = startitem;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
{
list_cert (ctx, item->cert, 1);
}
}
/* Process an encrypt operation's log. */
static void
proc_type_encrypt (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int recp_no, idx;
char numbuf[35];
int algo;
char *name;
item = find_log_item (ctx, AUDIT_ENCRYPTION_DONE, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data encryption succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
item = find_log_item (ctx, AUDIT_SESSION_KEY, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Session key created"));
if (item)
{
algo = gcry_cipher_map_name (item->string);
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
else if (item->string)
writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
else
writeout_rem (ctx, _("seems to be not encrypted"));
}
item = find_log_item (ctx, AUDIT_GOT_RECIPIENTS, 0);
snprintf (numbuf, sizeof numbuf, "%d",
item && item->have_intvalue? item->intvalue : 0);
writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
/* Loop over all recipients. */
loopitem = NULL;
recp_no = 0;
while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_ENCRYPTED_TO, 0)))
{
recp_no++;
writeout_li (ctx, NULL, _("Recipient %d"), recp_no);
if (loopitem->cert)
{
name = get_cert_name (loopitem->cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a sign operation's log. */
static void
proc_type_sign (audit_ctx_t ctx)
{
log_item_t item, loopitem;
int signer, idx;
const char *result;
ksba_cert_t cert;
char *name;
int lastalgo;
item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data signing succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
/* Write remarks with the data hash algorithms. We use a very
simple scheme to avoid some duplicates. */
loopitem = NULL;
lastalgo = 0;
while ((loopitem = find_next_log_item
(ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG)))
{
if (loopitem->intvalue && loopitem->intvalue != lastalgo)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (loopitem->intvalue));
lastalgo = loopitem->intvalue;
}
/* Loop over all signer. */
loopitem = NULL;
signer = 0;
while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)))
{
signer++;
item = find_next_log_item (ctx, loopitem, AUDIT_SIGNED_BY, AUDIT_NEW_SIG);
if (!item)
result = "error";
else if (!item->err)
result = "okay";
else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
result = "skipped";
else
result = gpg_strerror (item->err);
cert = item? item->cert : NULL;
writeout_li (ctx, result, _("Signer %d"), signer);
item = find_next_log_item (ctx, loopitem,
AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("attr hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
if (cert)
{
name = get_cert_name (cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a decrypt operation's log. */
static void
proc_type_decrypt (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int algo, recpno;
char *name;
char numbuf[35];
int idx;
item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0);
writeout_li (ctx, item && !item->err?"Yes":"No",
"%s", _("Data decryption succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
item = find_log_item (ctx, AUDIT_DATA_CIPHER_ALGO, 0);
algo = item? item->intvalue : 0;
writeout_li (ctx, algo?"Yes":"No", "%s", _("Encryption algorithm supported"));
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0);
if (item && item->string)
{
algo = gcry_cipher_map_name (item->string);
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
else if (item->string)
writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
else
writeout_rem (ctx, _("seems to be not encrypted"));
}
for (recpno = 0, item = NULL;
(item = find_next_log_item (ctx, item, AUDIT_NEW_RECP, 0)); recpno++)
;
snprintf (numbuf, sizeof numbuf, "%d", recpno);
writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
/* Loop over all recipients. */
loopitem = NULL;
while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_RECP, 0)))
{
const char *result;
recpno = loopitem->have_intvalue? loopitem->intvalue : -1;
item = find_next_log_item (ctx, loopitem,
AUDIT_RECP_RESULT, AUDIT_NEW_RECP);
if (!item)
result = "not-used";
else if (!item->err)
result = "okay";
else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
result = "skipped";
else
result = gpg_strerror (item->err);
item = find_next_log_item (ctx, loopitem,
AUDIT_RECP_NAME, AUDIT_NEW_RECP);
writeout_li (ctx, result, _("Recipient %d"), recpno);
if (item && item->string)
writeout_rem (ctx, "%s", item->string);
/* If we have a certificate write out more infos. */
item = find_next_log_item (ctx, loopitem,
AUDIT_SAVE_CERT, AUDIT_NEW_RECP);
if (item && item->cert)
{
enter_li (ctx);
for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a verification operation's log. */
static void
proc_type_verify (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int signo, count, idx, n_good, n_bad;
char numbuf[35];
const char *result;
/* If there is at least one signature status we claim that the
verification succeeded. This does not mean that the data has
verified okay. */
item = find_log_item (ctx, AUDIT_SIG_STATUS, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
if (!item)
goto leave;
item = find_log_item (ctx, AUDIT_NEW_SIG, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available"));
if (!item)
goto leave;
/* Print info about the used data hashing algorithms. */
for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++)
{
item = ctx->log + idx;
if (item->event == AUDIT_NEW_SIG)
break;
else if (item->event == AUDIT_DATA_HASH_ALGO)
n_good++;
else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
n_bad++;
}
item = find_log_item (ctx, AUDIT_DATA_HASHING, AUDIT_NEW_SIG);
if (!item || item->err || !n_good)
result = "No";
else if (n_good && !n_bad)
result = "Yes";
else
result = "Some";
writeout_li (ctx, result, "%s", _("Parsing data succeeded"));
if (n_good || n_bad)
{
for (idx=0; idx < ctx->logused; idx++)
{
item = ctx->log + idx;
if (item->event == AUDIT_NEW_SIG)
break;
else if (item->event == AUDIT_DATA_HASH_ALGO)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
writeout_rem (ctx, _("bad data hash algorithm: %s"),
item->string? item->string:"?");
}
}
/* Loop over all signatures. */
loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0);
assert (loopitem);
do
{
signo = loopitem->have_intvalue? loopitem->intvalue : -1;
item = find_next_log_item (ctx, loopitem,
AUDIT_SIG_STATUS, AUDIT_NEW_SIG);
writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo);
item = find_next_log_item (ctx, loopitem,
AUDIT_SIG_NAME, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, "%s", item->string);
item = find_next_log_item (ctx, loopitem,
AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
item = find_next_log_item (ctx, loopitem,
AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("attr hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
enter_li (ctx);
/* List the certificate chain. */
list_certchain (ctx, loopitem, AUDIT_NEW_SIG);
/* Show the result of the chain validation. */
item = find_next_log_item (ctx, loopitem,
AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes",
_("Certificate chain valid"));
if (item->err)
writeout_rem (ctx, "%s", gpg_strerror (item->err));
}
/* Show whether the root certificate is fine. */
item = find_next_log_item (ctx, loopitem,
AUDIT_ROOT_TRUSTED, AUDIT_CHAIN_STATUS);
if (item)
{
writeout_li (ctx, item->err?"No":"Yes", "%s",
_("Root certificate trustworthy"));
if (item->err)
{
add_helptag (ctx, "gpgsm.root-cert-not-trusted");
writeout_rem (ctx, "%s", gpg_strerror (item->err));
list_cert (ctx, item->cert, 0);
}
}
/* Show result of the CRL/OCSP check. */
item = find_next_log_item (ctx, loopitem,
AUDIT_CRL_CHECK, AUDIT_NEW_SIG);
if (item)
{
const char *ok;
switch (gpg_err_code (item->err))
{
case 0: ok = "good"; break;
case GPG_ERR_CERT_REVOKED: ok = "bad"; break;
case GPG_ERR_NOT_ENABLED: ok = "disabled"; break;
case GPG_ERR_NO_CRL_KNOWN:
ok = _("no CRL found for certificate");
break;
case GPG_ERR_CRL_TOO_OLD:
ok = _("the available CRL is too old");
break;
default: ok = gpg_strerror (item->err); break;
}
writeout_li (ctx, ok, "%s", _("CRL/OCSP check of certificates"));
if (item->err
&& gpg_err_code (item->err) != GPG_ERR_CERT_REVOKED
&& gpg_err_code (item->err) != GPG_ERR_NOT_ENABLED)
add_helptag (ctx, "gpgsm.crl-problem");
}
leave_li (ctx);
}
while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)));
leave:
/* Always list the certificates stored in the signature. */
item = NULL;
count = 0;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
count++;
snprintf (numbuf, sizeof numbuf, "%d", count);
writeout_li (ctx, numbuf, _("Included certificates"));
item = NULL;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
{
char *name = get_cert_name (item->cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
leave_li (ctx);
}
/* Print the formatted audit result. THIS IS WORK IN PROGRESS. */
void
audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
{
int idx;
size_t n;
log_item_t item;
helptag_t helptag;
const char *s;
int show_raw = 0;
char *orig_codeset;
if (!ctx)
return;
orig_codeset = i18n_switchto_utf8 ();
/* We use an environment variable to include some debug info in the
log. */
if ((s = getenv ("gnupg_debug_audit")))
show_raw = 1;
assert (!ctx->outstream);
ctx->outstream = out;
ctx->use_html = use_html;
ctx->indentlevel = 0;
clear_helptags (ctx);
if (use_html)
es_fputs ("<div class=\"" GNUPG_NAME "AuditLog\">\n", ctx->outstream);
if (!ctx->log || !ctx->logused)
{
writeout_para (ctx, _("No audit log entries."));
goto leave;
}
if (show_raw)
{
int maxlen;
for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
{
n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);
if (n > maxlen)
maxlen = n;
}
if (use_html)
es_fputs ("<pre>\n", out);
for (idx=0; idx < ctx->logused; idx++)
{
es_fprintf (out, "log: %-*s",
maxlen, event2str (ctx->log[idx].event));
if (ctx->log[idx].have_intvalue)
es_fprintf (out, " i=%d", ctx->log[idx].intvalue);
if (ctx->log[idx].string)
{
es_fputs (" s='", out);
writeout (ctx, ctx->log[idx].string);
es_fputs ("'", out);
}
if (ctx->log[idx].cert)
es_fprintf (out, " has_cert");
if (ctx->log[idx].have_err)
{
es_fputs (" err='", out);
writeout (ctx, gpg_strerror (ctx->log[idx].err));
es_fputs ("'", out);
}
es_fputs ("\n", out);
}
if (use_html)
es_fputs ("</pre>\n", out);
else
es_fputs ("\n", out);
}
enter_li (ctx);
switch (ctx->type)
{
case AUDIT_TYPE_NONE:
writeout_li (ctx, NULL, _("Unknown operation"));
break;
case AUDIT_TYPE_ENCRYPT:
proc_type_encrypt (ctx);
break;
case AUDIT_TYPE_SIGN:
proc_type_sign (ctx);
break;
case AUDIT_TYPE_DECRYPT:
proc_type_decrypt (ctx);
break;
case AUDIT_TYPE_VERIFY:
proc_type_verify (ctx);
break;
}
item = find_log_item (ctx, AUDIT_AGENT_READY, 0);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes", "%s", _("Gpg-Agent usable"));
if (item->err)
{
writeout_rem (ctx, "%s", gpg_strerror (item->err));
add_helptag (ctx, "gnupg.agent-problem");
}
}
item = find_log_item (ctx, AUDIT_DIRMNGR_READY, 0);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes", "%s", _("Dirmngr usable"));
if (item->err)
{
writeout_rem (ctx, "%s", gpg_strerror (item->err));
add_helptag (ctx, "gnupg.dirmngr-problem");
}
}
leave_li (ctx);
/* Show the help from the collected help tags. */
if (ctx->helptags)
{
if (use_html)
{
es_fputs ("<hr/>\n", ctx->outstream);
if (ctx->helptags->next)
es_fputs ("<ul>\n", ctx->outstream);
}
else
es_fputs ("\n\n", ctx->outstream);
}
for (helptag = ctx->helptags; helptag; helptag = helptag->next)
{
char *text;
if (use_html && ctx->helptags->next)
es_fputs ("<li>\n", ctx->outstream);
text = gnupg_get_help_string (helptag->name, 0);
if (text)
{
writeout_para (ctx, "%s", text);
xfree (text);
}
else
writeout_para (ctx, _("No help available for '%s'."), helptag->name);
if (use_html && ctx->helptags->next)
es_fputs ("</li>\n", ctx->outstream);
if (helptag->next)
es_fputs ("\n", ctx->outstream);
}
if (use_html && ctx->helptags && ctx->helptags->next)
es_fputs ("</ul>\n", ctx->outstream);
leave:
if (use_html)
es_fputs ("</div>\n", ctx->outstream);
ctx->outstream = NULL;
ctx->use_html = 0;
clear_helptags (ctx);
i18n_switchback (orig_codeset);
}
diff --git a/common/dotlock.c b/common/dotlock.c
index 2dc143001..8d9ee5139 100644
--- a/common/dotlock.c
+++ b/common/dotlock.c
@@ -1,1379 +1,1379 @@
/* dotlock.c - dotfile locking
* Copyright (C) 1998, 2000, 2001, 2003, 2004,
* 2005, 2006, 2008, 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute and/or modify this
* part of GnuPG under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copies of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, see <https://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, this file may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU Lesser General License or the GNU
* General Public License. If you wish to allow use of your version of
* this file only under the terms of the GNU Lesser General License or
* the GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR 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 AUTHOR 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.
*/
/*
Overview:
=========
This module implements advisory file locking in a portable way.
Due to the problems with POSIX fcntl locking a separate lock file
is used. It would be possible to use fcntl locking on this lock
file and thus avoid the weird auto unlock bug of POSIX while still
having an unproved better performance of fcntl locking. However
there are still problems left, thus we resort to use a hardlink
which has the well defined property that a link call will fail if
the target file already exists.
Given that hardlinks are also available on NTFS file systems since
Windows XP; it will be possible to enhance this module to use
hardlinks even on Windows and thus allow Windows and Posix clients
to use locking on the same directory. This is not yet implemented;
instead we use a lockfile on Windows along with W32 style file
locking.
On FAT file systems hardlinks are not supported. Thus this method
does not work. Our solution is to use a O_EXCL locking instead.
Querying the type of the file system is not easy to do in a
portable way (e.g. Linux has a statfs, BSDs have a the same call
but using different structures and constants). What we do instead
is to check at runtime whether link(2) works for a specific lock
file.
How to use:
===========
At program initialization time, the module should be explicitly
initialized:
dotlock_create (NULL, 0);
This installs an atexit handler and may also initialize mutex etc.
It is optional for non-threaded applications. Only the first call
has an effect. This needs to be done before any extra threads are
started.
To create a lock file (which prepares it but does not take the
lock) you do:
dotlock_t h
h = dotlock_create (fname, 0);
if (!h)
error ("error creating lock file: %s\n", strerror (errno));
It is important to handle the error. For example on a read-only
file system a lock can't be created (but is usually not needed).
FNAME is the file you want to lock; the actual lockfile is that
name with the suffix ".lock" appended. On success a handle to be
used with the other functions is returned or NULL on error. Note
that the handle shall only be used by one thread at a time. This
function creates a unique file temporary file (".#lk*") in the same
directory as FNAME and returns a handle for further operations.
- The module keeps track of theses unique files so that they will be
+ The module keeps track of these unique files so that they will be
unlinked using the atexit handler. If you don't need the lock file
anymore, you may also explicitly remove it with a call to:
dotlock_destroy (h);
To actually lock the file, you use:
if (dotlock_take (h, -1))
error ("error taking lock: %s\n", strerror (errno));
This function will wait until the lock is acquired. If an
unexpected error occurs if will return non-zero and set ERRNO. If
you pass (0) instead of (-1) the function does not wait in case the
file is already locked but returns -1 and sets ERRNO to EACCES.
Any other positive value for the second parameter is considered a
timeout value in milliseconds.
To release the lock you call:
if (dotlock_release (h))
error ("error releasing lock: %s\n", strerror (errno));
or, if the lock file is not anymore needed, you may just call
dotlock_destroy. However dotlock_release does some extra checks
before releasing the lock and prints diagnostics to help detecting
bugs.
If you want to explicitly destroy all lock files you may call
dotlock_remove_lockfiles ();
which is the core of the installed atexit handler. In case your
application wants to disable locking completely it may call
disable_locking ()
before any locks are created.
There are two convenience functions to store an integer (e.g. a
file descriptor) value with the handle:
void dotlock_set_fd (dotlock_t h, int fd);
int dotlock_get_fd (dotlock_t h);
If nothing has been stored dotlock_get_fd returns -1.
How to build:
=============
This module was originally developed for GnuPG but later changed to
allow its use without any GnuPG dependency. If you want to use it
with you application you may simply use it and it should figure out
most things automagically.
You may use the common config.h file to pass macros, but take care
to pass -DHAVE_CONFIG_H to the compiler. Macros used by this
module are:
DOTLOCK_USE_PTHREAD - Define if POSIX threads are in use.
DOTLOCK_GLIB_LOGGING - Define this to use Glib logging functions.
DOTLOCK_EXT_SYM_PREFIX - Prefix all external symbols with the
string to which this macro evaluates.
GNUPG_MAJOR_VERSION - Defined when used by GnuPG.
HAVE_DOSISH_SYSTEM - Defined for Windows etc. Will be
automatically defined if a the target is
Windows.
HAVE_POSIX_SYSTEM - Internally defined to !HAVE_DOSISH_SYSTEM.
HAVE_SIGNAL_H - Should be defined on Posix systems. If config.h
is not used defaults to defined.
DIRSEP_C - Separation character for file name parts.
Usually not redefined.
EXTSEP_S - Separation string for file name suffixes.
Usually not redefined.
HAVE_W32CE_SYSTEM - Currently only used by GnuPG.
Note that there is a test program t-dotlock which has compile
instructions at its end. At least for SMBFS and CIFS it is
important that 64 bit versions of stat are used; most programming
environments do this these days, just in case you want to compile
it on the command line, remember to pass -D_FILE_OFFSET_BITS=64
Bugs:
=====
On Windows this module is not yet thread-safe.
Miscellaneous notes:
====================
On hardlinks:
- Hardlinks are supported under Windows with NTFS since XP/Server2003.
- In Linux 2.6.33 both SMBFS and CIFS seem to support hardlinks.
- NFS supports hard links. But there are solvable problems.
- FAT does not support links
On the file locking API:
- CIFS on Linux 2.6.33 supports several locking methods.
SMBFS seems not to support locking. No closer checks done.
- NFS supports Posix locks. flock is emulated in the server.
However there are a couple of problems; see below.
- FAT does not support locks.
- An advantage of fcntl locking is that R/W locks can be
implemented which is not easy with a straight lock file.
On O_EXCL:
- Does not work reliable on NFS
- Should work on CIFS and SMBFS but how can we delete lockfiles?
On NFS problems:
- Locks vanish if the server crashes and reboots.
- Client crashes keep the lock in the server until the client
re-connects.
- Communication problems may return unreliable error codes. The
MUA Postfix's workaround is to compare the link count after
seeing an error for link. However that gives a race. If using a
unique file to link to a lockfile and using stat to check the
link count instead of looking at the error return of link(2) is
the best solution.
- O_EXCL seems to have a race and may re-create a file anyway.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* Some quick replacements for stuff we usually expect to be defined
in config.h. Define HAVE_POSIX_SYSTEM for better readability. */
#if !defined (HAVE_DOSISH_SYSTEM) && defined(_WIN32)
# define HAVE_DOSISH_SYSTEM 1
#endif
#if !defined (HAVE_DOSISH_SYSTEM) && !defined (HAVE_POSIX_SYSTEM)
# define HAVE_POSIX_SYSTEM 1
#endif
/* With no config.h assume that we have sitgnal.h. */
#if !defined (HAVE_CONFIG_H) && defined (HAVE_POSIX_SYSTEM)
# define HAVE_SIGNAL_H 1
#endif
/* Standard headers. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_DOSISH_SYSTEM
# define WIN32_LEAN_AND_MEAN /* We only need the OS core stuff. */
# include <windows.h>
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/utsname.h>
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef DOTLOCK_USE_PTHREAD
# include <pthread.h>
#endif
#ifdef DOTLOCK_GLIB_LOGGING
# include <glib.h>
#endif
#ifdef GNUPG_MAJOR_VERSION
# include "util.h"
# include "common-defs.h"
# include "stringhelp.h" /* For stpcpy and w32_strerror. */
#endif
#ifdef HAVE_W32CE_SYSTEM
# include "utf8conv.h" /* WindowsCE requires filename conversion. */
#endif
#include "dotlock.h"
/* Define constants for file name construction. */
#if !defined(DIRSEP_C) && !defined(EXTSEP_S)
# ifdef HAVE_DOSISH_SYSTEM
# define DIRSEP_C '\\'
# define EXTSEP_S "."
#else
# define DIRSEP_C '/'
# define EXTSEP_S "."
# endif
#endif
/* In GnuPG we use wrappers around the malloc functions. If they are
not defined we assume that this code is used outside of GnuPG and
fall back to the regular malloc functions. */
#ifndef xtrymalloc
# define xtrymalloc(a) malloc ((a))
# define xtrycalloc(a,b) calloc ((a), (b))
# define xfree(a) free ((a))
#endif
/* Wrapper to set ERRNO (required for W32CE). */
#ifdef GPG_ERROR_VERSION
# define my_set_errno(e) gpg_err_set_errno ((e))
#else
# define my_set_errno(e) do { errno = (e); } while (0)
#endif
/* Gettext macro replacement. */
#ifndef _
# define _(a) (a)
#endif
#ifdef GNUPG_MAJOR_VERSION
# define my_info_0(a) log_info ((a))
# define my_info_1(a,b) log_info ((a), (b))
# define my_info_2(a,b,c) log_info ((a), (b), (c))
# define my_info_3(a,b,c,d) log_info ((a), (b), (c), (d))
# define my_error_0(a) log_error ((a))
# define my_error_1(a,b) log_error ((a), (b))
# define my_error_2(a,b,c) log_error ((a), (b), (c))
# define my_debug_1(a,b) log_debug ((a), (b))
# define my_fatal_0(a) log_fatal ((a))
#elif defined (DOTLOCK_GLIB_LOGGING)
# define my_info_0(a) g_message ((a))
# define my_info_1(a,b) g_message ((a), (b))
# define my_info_2(a,b,c) g_message ((a), (b), (c))
# define my_info_3(a,b,c,d) g_message ((a), (b), (c), (d))
# define my_error_0(a) g_warning ((a))
# define my_error_1(a,b) g_warning ((a), (b))
# define my_error_2(a,b,c) g_warning ((a), (b), (c))
# define my_debug_1(a,b) g_debug ((a), (b))
# define my_fatal_0(a) g_error ((a))
#else
# define my_info_0(a) fprintf (stderr, (a))
# define my_info_1(a,b) fprintf (stderr, (a), (b))
# define my_info_2(a,b,c) fprintf (stderr, (a), (b), (c))
# define my_info_3(a,b,c,d) fprintf (stderr, (a), (b), (c), (d))
# define my_error_0(a) fprintf (stderr, (a))
# define my_error_1(a,b) fprintf (stderr, (a), (b))
# define my_error_2(a,b,c) fprintf (stderr, (a), (b), (c))
# define my_debug_1(a,b) fprintf (stderr, (a), (b))
# define my_fatal_0(a) do { fprintf (stderr,(a)); fflush (stderr); \
abort (); } while (0)
#endif
/* The object describing a lock. */
struct dotlock_handle
{
struct dotlock_handle *next;
char *lockname; /* Name of the actual lockfile. */
unsigned int locked:1; /* Lock status. */
unsigned int disable:1; /* If true, locking is disabled. */
unsigned int use_o_excl:1; /* Use open (O_EXCL) for locking. */
int extra_fd; /* A place for the caller to store an FD. */
#ifdef HAVE_DOSISH_SYSTEM
HANDLE lockhd; /* The W32 handle of the lock file. */
#else /*!HAVE_DOSISH_SYSTEM */
char *tname; /* Name of the lockfile template. */
size_t nodename_off; /* Offset in TNAME of the nodename part. */
size_t nodename_len; /* Length of the nodename part. */
#endif /*!HAVE_DOSISH_SYSTEM */
};
/* A list of all lock handles. The volatile attribute might help
if used in an atexit handler. Note that [UN]LOCK_all_lockfiles
must not change ERRNO. */
static volatile dotlock_t all_lockfiles;
#ifdef DOTLOCK_USE_PTHREAD
static pthread_mutex_t all_lockfiles_mutex = PTHREAD_MUTEX_INITIALIZER;
# define LOCK_all_lockfiles() do { \
if (pthread_mutex_lock (&all_lockfiles_mutex)) \
my_fatal_0 ("locking all_lockfiles_mutex failed\n"); \
} while (0)
# define UNLOCK_all_lockfiles() do { \
if (pthread_mutex_unlock (&all_lockfiles_mutex)) \
my_fatal_0 ("unlocking all_lockfiles_mutex failed\n"); \
} while (0)
#else /*!DOTLOCK_USE_PTHREAD*/
# define LOCK_all_lockfiles() do { } while (0)
# define UNLOCK_all_lockfiles() do { } while (0)
#endif /*!DOTLOCK_USE_PTHREAD*/
/* If this has the value true all locking is disabled. */
static int never_lock;
#ifdef HAVE_DOSISH_SYSTEM
static int
map_w32_to_errno (DWORD w32_err)
{
switch (w32_err)
{
case 0:
return 0;
case ERROR_FILE_NOT_FOUND:
return ENOENT;
case ERROR_PATH_NOT_FOUND:
return ENOENT;
case ERROR_ACCESS_DENIED:
return EPERM;
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_BLOCK:
return EINVAL;
case ERROR_NOT_ENOUGH_MEMORY:
return ENOMEM;
case ERROR_NO_DATA:
case ERROR_BROKEN_PIPE:
return EPIPE;
default:
return EIO;
}
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Entirely disable all locking. This function should be called
before any locking is done. It may be called right at startup of
the process as it only sets a global value. */
void
dotlock_disable (void)
{
never_lock = 1;
}
#ifdef HAVE_POSIX_SYSTEM
static int
maybe_deadlock (dotlock_t h)
{
dotlock_t r;
int res = 0;
LOCK_all_lockfiles ();
for (r=all_lockfiles; r; r = r->next)
{
if ( r != h && r->locked )
{
res = 1;
break;
}
}
UNLOCK_all_lockfiles ();
return res;
}
#endif /*HAVE_POSIX_SYSTEM*/
/* Read the lock file and return the pid, returns -1 on error. True
will be stored in the integer at address SAME_NODE if the lock file
has been created on the same node. */
#ifdef HAVE_POSIX_SYSTEM
static int
read_lockfile (dotlock_t h, int *same_node )
{
char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node
names are usually shorter. */
int fd;
int pid = -1;
char *buffer, *p;
size_t expected_len;
int res, nread;
*same_node = 0;
expected_len = 10 + 1 + h->nodename_len + 1;
if ( expected_len >= sizeof buffer_space)
{
buffer = xtrymalloc (expected_len);
if (!buffer)
return -1;
}
else
buffer = buffer_space;
if ( (fd = open (h->lockname, O_RDONLY)) == -1 )
{
int e = errno;
my_info_2 ("error opening lockfile '%s': %s\n",
h->lockname, strerror(errno) );
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (e); /* Need to return ERRNO here. */
return -1;
}
p = buffer;
nread = 0;
do
{
res = read (fd, p, expected_len - nread);
if (res == -1 && errno == EINTR)
continue;
if (res < 0)
{
int e = errno;
my_info_1 ("error reading lockfile '%s'\n", h->lockname );
close (fd);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (e);
return -1;
}
p += res;
nread += res;
}
while (res && nread != expected_len);
close(fd);
if (nread < 11)
{
my_info_1 ("invalid size of lockfile '%s'\n", h->lockname);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (EINVAL);
return -1;
}
if (buffer[10] != '\n'
|| (buffer[10] = 0, pid = atoi (buffer)) == -1
|| !pid )
{
my_error_2 ("invalid pid %d in lockfile '%s'\n", pid, h->lockname);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (EINVAL);
return -1;
}
if (nread == expected_len
&& !memcmp (h->tname+h->nodename_off, buffer+11, h->nodename_len)
&& buffer[11+h->nodename_len] == '\n')
*same_node = 1;
if (buffer != buffer_space)
xfree (buffer);
return pid;
}
#endif /*HAVE_POSIX_SYSTEM */
/* Check whether the file system which stores TNAME supports
hardlinks. Instead of using the non-portable statsfs call which
differs between various Unix versions, we do a runtime test.
Returns: 0 supports hardlinks; 1 no hardlink support, -1 unknown
(test error). */
#ifdef HAVE_POSIX_SYSTEM
static int
use_hardlinks_p (const char *tname)
{
char *lname;
struct stat sb;
unsigned int nlink;
int res;
if (stat (tname, &sb))
return -1;
nlink = (unsigned int)sb.st_nlink;
lname = xtrymalloc (strlen (tname) + 1 + 1);
if (!lname)
return -1;
strcpy (lname, tname);
strcat (lname, "x");
/* We ignore the return value of link() because it is unreliable. */
(void) link (tname, lname);
if (stat (tname, &sb))
res = -1; /* Ooops. */
else if (sb.st_nlink == nlink + 1)
res = 0; /* Yeah, hardlinks are supported. */
else
res = 1; /* No hardlink support. */
unlink (lname);
xfree (lname);
return res;
}
#endif /*HAVE_POSIX_SYSTEM */
#ifdef HAVE_POSIX_SYSTEM
/* Locking core for Unix. It used a temporary file and the link
system call to make locking an atomic operation. */
static dotlock_t
dotlock_create_unix (dotlock_t h, const char *file_to_lock)
{
int fd = -1;
char pidstr[16];
const char *nodename;
const char *dirpart;
int dirpartlen;
struct utsname utsbuf;
size_t tnamelen;
snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() );
/* Create a temporary file. */
if ( uname ( &utsbuf ) )
nodename = "unknown";
else
nodename = utsbuf.nodename;
if ( !(dirpart = strrchr (file_to_lock, DIRSEP_C)) )
{
dirpart = EXTSEP_S;
dirpartlen = 1;
}
else
{
dirpartlen = dirpart - file_to_lock;
dirpart = file_to_lock;
}
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10 + 1;
h->tname = xtrymalloc (tnamelen + 1);
if (!h->tname)
{
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
xfree (h);
return NULL;
}
h->nodename_len = strlen (nodename);
snprintf (h->tname, tnamelen, "%.*s/.#lk%p.", dirpartlen, dirpart, h );
h->nodename_off = strlen (h->tname);
snprintf (h->tname+h->nodename_off, tnamelen - h->nodename_off,
"%s.%d", nodename, (int)getpid ());
do
{
my_set_errno (0);
fd = open (h->tname, O_WRONLY|O_CREAT|O_EXCL,
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
}
while (fd == -1 && errno == EINTR);
if ( fd == -1 )
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("failed to create temporary file '%s': %s\n"),
h->tname, strerror (errno));
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
if ( write (fd, pidstr, 11 ) != 11 )
goto write_failed;
if ( write (fd, nodename, strlen (nodename) ) != strlen (nodename) )
goto write_failed;
if ( write (fd, "\n", 1 ) != 1 )
goto write_failed;
if ( close (fd) )
{
if ( errno == EINTR )
fd = -1;
goto write_failed;
}
fd = -1;
/* Check whether we support hard links. */
switch (use_hardlinks_p (h->tname))
{
case 0: /* Yes. */
break;
case 1: /* No. */
unlink (h->tname);
h->use_o_excl = 1;
break;
default:
{
int saveerrno = errno;
my_error_2 ("can't check whether hardlinks are supported for '%s': %s\n"
, h->tname, strerror (saveerrno));
my_set_errno (saveerrno);
}
goto write_failed;
}
h->lockname = xtrymalloc (strlen (file_to_lock) + 6 );
if (!h->lockname)
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
unlink (h->tname);
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock");
UNLOCK_all_lockfiles ();
if (h->use_o_excl)
my_debug_1 ("locking for '%s' done via O_EXCL\n", h->lockname);
return h;
write_failed:
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("error writing to '%s': %s\n"), h->tname, strerror (errno));
if ( fd != -1 )
close (fd);
unlink (h->tname);
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
}
return NULL;
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Locking core for Windows. This version does not need a temporary
file but uses the plain lock file along with record locking. We
create this file here so that we later only need to do the file
locking. For error reporting it is useful to keep the name of the
file in the handle. */
static dotlock_t
dotlock_create_w32 (dotlock_t h, const char *file_to_lock)
{
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
h->lockname = xtrymalloc ( strlen (file_to_lock) + 6 );
if (!h->lockname)
{
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
xfree (h);
return NULL;
}
strcpy (stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock");
/* If would be nice if we would use the FILE_FLAG_DELETE_ON_CLOSE
along with FILE_SHARE_DELETE but that does not work due to a race
condition: Despite the OPEN_ALWAYS flag CreateFile may return an
error and we can't reliable create/open the lock file unless we
would wait here until it works - however there are other valid
reasons why a lock file can't be created and thus the process
would not stop as expected but spin until Windows crashes. Our
solution is to keep the lock file open; that does not harm. */
{
#ifdef HAVE_W32CE_SYSTEM
wchar_t *wname = utf8_to_wchar (h->lockname);
if (wname)
h->lockhd = CreateFile (wname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, 0, NULL);
else
h->lockhd = INVALID_HANDLE_VALUE;
xfree (wname);
#else
h->lockhd = CreateFile (h->lockname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, 0, NULL);
#endif
}
if (h->lockhd == INVALID_HANDLE_VALUE)
{
int saveerrno = map_w32_to_errno (GetLastError ());
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("can't create '%s': %s\n"), h->lockname, w32_strerror (-1));
xfree (h->lockname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
return h;
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Create a lockfile for a file name FILE_TO_LOCK and returns an
object of type dotlock_t which may be used later to actually acquire
the lock. A cleanup routine gets installed to cleanup left over
locks or other files used internally by the lock mechanism.
Calling this function with NULL does only install the atexit
handler and may thus be used to assure that the cleanup is called
after all other atexit handlers.
This function creates a lock file in the same directory as
FILE_TO_LOCK using that name and a suffix of ".lock". Note that on
POSIX systems a temporary file ".#lk.<hostname>.pid[.threadid] is
used.
FLAGS must be 0.
The function returns an new handle which needs to be released using
destroy_dotlock but gets also released at the termination of the
process. On error NULL is returned.
*/
dotlock_t
dotlock_create (const char *file_to_lock, unsigned int flags)
{
static int initialized;
dotlock_t h;
if ( !initialized )
{
atexit (dotlock_remove_lockfiles);
initialized = 1;
}
if ( !file_to_lock )
return NULL; /* Only initialization was requested. */
if (flags)
{
my_set_errno (EINVAL);
return NULL;
}
h = xtrycalloc (1, sizeof *h);
if (!h)
return NULL;
h->extra_fd = -1;
if (never_lock)
{
h->disable = 1;
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
UNLOCK_all_lockfiles ();
return h;
}
#ifdef HAVE_DOSISH_SYSTEM
return dotlock_create_w32 (h, file_to_lock);
#else /*!HAVE_DOSISH_SYSTEM */
return dotlock_create_unix (h, file_to_lock);
#endif /*!HAVE_DOSISH_SYSTEM*/
}
/* Convenience function to store a file descriptor (or any other
integer value) in the context of handle H. */
void
dotlock_set_fd (dotlock_t h, int fd)
{
h->extra_fd = fd;
}
/* Convenience function to retrieve a file descriptor (or any other
integer value) stored in the context of handle H. */
int
dotlock_get_fd (dotlock_t h)
{
return h->extra_fd;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of destroy_dotlock. */
static void
dotlock_destroy_unix (dotlock_t h)
{
if (h->locked && h->lockname)
unlink (h->lockname);
if (h->tname && !h->use_o_excl)
unlink (h->tname);
xfree (h->tname);
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of destroy_dotlock. */
static void
dotlock_destroy_w32 (dotlock_t h)
{
if (h->locked)
{
OVERLAPPED ovl;
memset (&ovl, 0, sizeof ovl);
UnlockFileEx (h->lockhd, 0, 1, 0, &ovl);
}
CloseHandle (h->lockhd);
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Destroy the lock handle H and release the lock. */
void
dotlock_destroy (dotlock_t h)
{
dotlock_t hprev, htmp;
if ( !h )
return;
/* First remove the handle from our global list of all locks. */
LOCK_all_lockfiles ();
for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next)
if (htmp == h)
{
if (hprev)
hprev->next = htmp->next;
else
all_lockfiles = htmp->next;
h->next = NULL;
break;
}
UNLOCK_all_lockfiles ();
/* Then destroy the lock. */
if (!h->disable)
{
#ifdef HAVE_DOSISH_SYSTEM
dotlock_destroy_w32 (h);
#else /* !HAVE_DOSISH_SYSTEM */
dotlock_destroy_unix (h);
#endif /* HAVE_DOSISH_SYSTEM */
xfree (h->lockname);
}
xfree(h);
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of make_dotlock. Returns 0 on success and -1 on
error. */
static int
dotlock_take_unix (dotlock_t h, long timeout)
{
int wtime = 0;
int sumtime = 0;
int pid;
int lastpid = -1;
int ownerchanged;
const char *maybe_dead="";
int same_node;
int saveerrno;
again:
if (h->use_o_excl)
{
/* No hardlink support - use open(O_EXCL). */
int fd;
do
{
my_set_errno (0);
fd = open (h->lockname, O_WRONLY|O_CREAT|O_EXCL,
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
}
while (fd == -1 && errno == EINTR);
if (fd == -1 && errno == EEXIST)
; /* Lock held by another process. */
else if (fd == -1)
{
saveerrno = errno;
my_error_2 ("lock not made: open(O_EXCL) of '%s' failed: %s\n",
h->lockname, strerror (saveerrno));
my_set_errno (saveerrno);
return -1;
}
else
{
char pidstr[16];
snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid());
if (write (fd, pidstr, 11 ) == 11
&& write (fd, h->tname + h->nodename_off,h->nodename_len)
== h->nodename_len
&& write (fd, "\n", 1) == 1
&& !close (fd))
{
h->locked = 1;
return 0;
}
/* Write error. */
saveerrno = errno;
my_error_2 ("lock not made: writing to '%s' failed: %s\n",
h->lockname, strerror (errno));
close (fd);
unlink (h->lockname);
my_set_errno (saveerrno);
return -1;
}
}
else /* Standard method: Use hardlinks. */
{
struct stat sb;
/* We ignore the return value of link() because it is unreliable. */
(void) link (h->tname, h->lockname);
if (stat (h->tname, &sb))
{
saveerrno = errno;
my_error_1 ("lock not made: Oops: stat of tmp file failed: %s\n",
strerror (errno));
/* In theory this might be a severe error: It is possible
that link succeeded but stat failed due to changed
permissions. We can't do anything about it, though. */
my_set_errno (saveerrno);
return -1;
}
if (sb.st_nlink == 2)
{
h->locked = 1;
return 0; /* Okay. */
}
}
/* Check for stale lock files. */
if ( (pid = read_lockfile (h, &same_node)) == -1 )
{
if ( errno != ENOENT )
{
saveerrno = errno;
my_info_0 ("cannot read lockfile\n");
my_set_errno (saveerrno);
return -1;
}
my_info_0 ("lockfile disappeared\n");
goto again;
}
else if ( pid == getpid() && same_node )
{
my_info_0 ("Oops: lock already held by us\n");
h->locked = 1;
return 0; /* okay */
}
else if ( same_node && kill (pid, 0) && errno == ESRCH )
{
- /* Note: It is unlikley that we get a race here unless a pid is
+ /* Note: It is unlikely that we get a race here unless a pid is
reused too fast or a new process with the same pid as the one
of the stale file tries to lock right at the same time as we. */
my_info_1 (_("removing stale lockfile (created by %d)\n"), pid);
unlink (h->lockname);
goto again;
}
if (lastpid == -1)
lastpid = pid;
ownerchanged = (pid != lastpid);
if (timeout)
{
struct timeval tv;
/* Wait until lock has been released. We use increasing retry
intervals of 50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s
but reset it if the lock owner meanwhile changed. */
if (!wtime || ownerchanged)
wtime = 50;
else if (wtime < 800)
wtime *= 2;
else if (wtime == 800)
wtime = 2000;
else if (wtime < 8000)
wtime *= 2;
if (timeout > 0)
{
if (wtime > timeout)
wtime = timeout;
timeout -= wtime;
}
sumtime += wtime;
if (sumtime >= 1500)
{
sumtime = 0;
my_info_3 (_("waiting for lock (held by %d%s) %s...\n"),
pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):"");
}
tv.tv_sec = wtime / 1000;
tv.tv_usec = (wtime % 1000) * 1000;
select (0, NULL, NULL, NULL, &tv);
goto again;
}
my_set_errno (EACCES);
return -1;
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of make_dotlock. Returns 0 on success and -1 on
error. */
static int
dotlock_take_w32 (dotlock_t h, long timeout)
{
int wtime = 0;
int w32err;
OVERLAPPED ovl;
again:
/* Lock one byte at offset 0. The offset is given by OVL. */
memset (&ovl, 0, sizeof ovl);
if (LockFileEx (h->lockhd, (LOCKFILE_EXCLUSIVE_LOCK
| LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ovl))
{
h->locked = 1;
return 0; /* okay */
}
w32err = GetLastError ();
if (w32err != ERROR_LOCK_VIOLATION)
{
my_error_2 (_("lock '%s' not made: %s\n"),
h->lockname, w32_strerror (w32err));
my_set_errno (map_w32_to_errno (w32err));
return -1;
}
if (timeout)
{
/* Wait until lock has been released. We use retry intervals of
50ms, 100ms, 200ms, 400ms, 800ms, 2s, 4s and 8s. */
if (!wtime)
wtime = 50;
else if (wtime < 800)
wtime *= 2;
else if (wtime == 800)
wtime = 2000;
else if (wtime < 8000)
wtime *= 2;
if (timeout > 0)
{
if (wtime > timeout)
wtime = timeout;
timeout -= wtime;
}
if (wtime >= 800)
my_info_1 (_("waiting for lock %s...\n"), h->lockname);
Sleep (wtime);
goto again;
}
my_set_errno (EACCES);
return -1;
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Take a lock on H. A value of 0 for TIMEOUT returns immediately if
the lock can't be taken, -1 waits forever (hopefully not), other
values wait for TIMEOUT milliseconds. Returns: 0 on success */
int
dotlock_take (dotlock_t h, long timeout)
{
int ret;
if ( h->disable )
return 0; /* Locks are completely disabled. Return success. */
if ( h->locked )
{
my_debug_1 ("Oops, '%s' is already locked\n", h->lockname);
return 0;
}
#ifdef HAVE_DOSISH_SYSTEM
ret = dotlock_take_w32 (h, timeout);
#else /*!HAVE_DOSISH_SYSTEM*/
ret = dotlock_take_unix (h, timeout);
#endif /*!HAVE_DOSISH_SYSTEM*/
return ret;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of release_dotlock. */
static int
dotlock_release_unix (dotlock_t h)
{
int pid, same_node;
int saveerrno;
pid = read_lockfile (h, &same_node);
if ( pid == -1 )
{
saveerrno = errno;
my_error_0 ("release_dotlock: lockfile error\n");
my_set_errno (saveerrno);
return -1;
}
if ( pid != getpid() || !same_node )
{
my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid);
my_set_errno (EACCES);
return -1;
}
if ( unlink( h->lockname ) )
{
saveerrno = errno;
my_error_1 ("release_dotlock: error removing lockfile '%s'\n",
h->lockname);
my_set_errno (saveerrno);
return -1;
}
/* Fixme: As an extra check we could check whether the link count is
now really at 1. */
return 0;
}
#endif /*HAVE_POSIX_SYSTEM */
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of release_dotlock. */
static int
dotlock_release_w32 (dotlock_t h)
{
OVERLAPPED ovl;
memset (&ovl, 0, sizeof ovl);
if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl))
{
int saveerrno = map_w32_to_errno (GetLastError ());
my_error_2 ("release_dotlock: error removing lockfile '%s': %s\n",
h->lockname, w32_strerror (-1));
my_set_errno (saveerrno);
return -1;
}
return 0;
}
#endif /*HAVE_DOSISH_SYSTEM */
/* Release a lock. Returns 0 on success. */
int
dotlock_release (dotlock_t h)
{
int ret;
/* To avoid atexit race conditions we first check whether there are
any locks left. It might happen that another atexit handler
tries to release the lock while the atexit handler of this module
already ran and thus H is undefined. */
LOCK_all_lockfiles ();
ret = !all_lockfiles;
UNLOCK_all_lockfiles ();
if (ret)
return 0;
if ( h->disable )
return 0;
if ( !h->locked )
{
my_debug_1 ("Oops, '%s' is not locked\n", h->lockname);
return 0;
}
#ifdef HAVE_DOSISH_SYSTEM
ret = dotlock_release_w32 (h);
#else
ret = dotlock_release_unix (h);
#endif
if (!ret)
h->locked = 0;
return ret;
}
/* Remove all lockfiles. This is called by the atexit handler
installed by this module but may also be called by other
termination handlers. */
void
dotlock_remove_lockfiles (void)
{
dotlock_t h, h2;
/* First set the lockfiles list to NULL so that for example
dotlock_release is aware that this function is currently
running. */
LOCK_all_lockfiles ();
h = all_lockfiles;
all_lockfiles = NULL;
UNLOCK_all_lockfiles ();
while ( h )
{
h2 = h->next;
dotlock_destroy (h);
h = h2;
}
}
diff --git a/common/exectool.c b/common/exectool.c
index 82b398542..49a619734 100644
--- a/common/exectool.c
+++ b/common/exectool.c
@@ -1,661 +1,661 @@
/* exectool.c - Utility functions to execute a helper tool
* Copyright (C) 2015 Werner Koch
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include <gpg-error.h>
#include <assuan.h>
#include "i18n.h"
#include "logging.h"
#include "membuf.h"
#include "mischelp.h"
#include "exechelp.h"
#include "sysutils.h"
#include "util.h"
#include "exectool.h"
typedef struct
{
const char *pgmname;
exec_tool_status_cb_t status_cb;
void *status_cb_value;
int cont;
int quiet;
size_t used;
size_t buffer_size;
char *buffer;
} read_and_log_buffer_t;
static inline gpg_error_t
my_error_from_syserror (void)
{
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
static void
read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr)
{
gpg_error_t err;
int c;
if (!fderr)
{
/* Flush internal buffer. */
if (state->used)
{
const char *pname;
int len;
state->buffer[state->used] = 0;
state->used = 0;
pname = strrchr (state->pgmname, '/');
if (pname && pname != state->pgmname && pname[1])
pname++;
else
pname = state->pgmname;
len = strlen (pname);
if (state->status_cb
&& !strncmp (state->buffer, "[GNUPG:] ", 9)
&& state->buffer[9] >= 'A' && state->buffer[9] <= 'Z')
{
char *rest;
rest = strchr (state->buffer + 9, ' ');
if (!rest)
{
/* Set REST to an empty string. */
rest = state->buffer + strlen (state->buffer);
}
else
{
*rest++ = 0;
trim_spaces (rest);
}
state->status_cb (state->status_cb_value,
state->buffer + 9, rest);
}
else if (state->quiet)
;
else if (!state->cont
&& !strncmp (state->buffer, pname, len)
&& strlen (state->buffer) > strlen (pname)
&& state->buffer[len] == ':' )
{
/* PGMNAME plus colon is identical to the start of
the output: print only the output. */
log_info ("%s\n", state->buffer);
}
else
log_info ("%s%c %s\n",
pname, state->cont? '+':':', state->buffer);
}
state->cont = 0;
return;
}
for (;;)
{
c = es_fgetc (fderr->stream);
if (c == EOF)
{
if (es_feof (fderr->stream))
{
fderr->ignore = 1; /* Not anymore needed. */
}
else if (es_ferror (fderr->stream))
{
err = my_error_from_syserror ();
log_error ("error reading stderr of '%s': %s\n",
state->pgmname, gpg_strerror (err));
fderr->ignore = 1; /* Disable. */
}
break;
}
else if (c == '\n')
{
read_and_log_stderr (state, NULL);
}
else
{
if (state->used >= state->buffer_size - 1)
{
if (state->status_cb)
{
/* A status callback requires that we have a full
* line. Thus we need to enlarget the buffer in
* this case. */
char *newbuffer;
size_t newsize = state->buffer_size + 256;
newbuffer = xtrymalloc (newsize);
if (!newbuffer)
{
log_error ("error allocating memory for status cb: %s\n",
gpg_strerror (my_error_from_syserror ()));
/* We better disable the status CB in this case. */
state->status_cb = NULL;
read_and_log_stderr (state, NULL);
state->cont = 1;
}
else
{
memcpy (newbuffer, state->buffer, state->used);
xfree (state->buffer);
state->buffer = newbuffer;
state->buffer_size = newsize;
}
}
else
{
read_and_log_stderr (state, NULL);
state->cont = 1;
}
}
state->buffer[state->used++] = c;
}
}
}
/* A buffer to copy from one stream to another. */
struct copy_buffer
{
char buffer[4096];
char *writep;
size_t nread;
};
/* Initialize a copy buffer. */
static void
copy_buffer_init (struct copy_buffer *c)
{
c->writep = c->buffer;
c->nread = 0;
}
/* Securely wipe a copy buffer. */
static void
copy_buffer_shred (struct copy_buffer *c)
{
if (c == NULL)
return;
wipememory (c->buffer, sizeof c->buffer);
c->writep = NULL;
c->nread = ~0U;
}
/* Copy data from SOURCE to SINK using copy buffer C. */
static gpg_error_t
copy_buffer_do_copy (struct copy_buffer *c, estream_t source, estream_t sink)
{
gpg_error_t err;
size_t nwritten = 0;
if (c->nread == 0)
{
c->writep = c->buffer;
if (es_read (source, c->buffer, sizeof c->buffer, &c->nread))
{
err = my_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
return 0; /* We will just retry next time. */
return err;
}
log_assert (c->nread <= sizeof c->buffer);
}
if (c->nread == 0)
return 0; /* Done copying. */
nwritten = 0;
if (sink && es_write (sink, c->writep, c->nread, &nwritten))
err = my_error_from_syserror ();
else
err = 0;
log_assert (nwritten <= c->nread);
c->writep += nwritten;
c->nread -= nwritten;
log_assert (c->writep - c->buffer <= sizeof c->buffer);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
return 0; /* We will just retry next time. */
return err;
}
if (sink && es_fflush (sink) && errno != EAGAIN)
err = my_error_from_syserror ();
return err;
}
/* Flush the remaining data to SINK. */
static gpg_error_t
copy_buffer_flush (struct copy_buffer *c, estream_t sink)
{
gpg_error_t err = 0;
size_t nwritten = 0;
if (es_write (sink, c->writep, c->nread, &nwritten))
err = my_error_from_syserror ();
log_assert (nwritten <= c->nread);
c->writep += nwritten;
c->nread -= nwritten;
log_assert (c->writep - c->buffer <= sizeof c->buffer);
if (err)
return err;
if (es_fflush (sink))
err = my_error_from_syserror ();
return err;
}
/* Run the program PGMNAME with the command line arguments given in
* the NULL terminates array ARGV. If INPUT is not NULL it will be
* fed to stdin of the process. stderr is logged using log_info and
- * the process' stdout is written to OUTPUT. If OUTPUT is NULL the
+ * the process's stdout is written to OUTPUT. If OUTPUT is NULL the
* output is discarded. If INEXTRA is given, an additional input
* stream will be passed to the child; to tell the child about this
* ARGV is scanned and the first occurrence of an argument
* "-&@INEXTRA@" is replaced by the concatenation of "-&" and the
* child's file descriptor of the pipe created for the INEXTRA stream.
*
* On error a diagnostic is printed and an error code returned. */
gpg_error_t
gnupg_exec_tool_stream (const char *pgmname, const char *argv[],
estream_t input, estream_t inextra,
estream_t output,
exec_tool_status_cb_t status_cb,
void *status_cb_value)
{
gpg_error_t err;
pid_t pid = (pid_t) -1;
estream_t infp = NULL;
estream_t extrafp = NULL;
estream_t outfp = NULL, errfp = NULL;
es_poll_t fds[4];
int exceptclose[2];
int extrapipe[2] = {-1, -1};
char extrafdbuf[20];
const char *argsave = NULL;
int argsaveidx;
int count;
read_and_log_buffer_t fderrstate;
struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL;
int quiet = 0;
int dummy_exitcode;
memset (fds, 0, sizeof fds);
memset (&fderrstate, 0, sizeof fderrstate);
/* If the first argument to the program is "--quiet" avoid all extra
* diagnostics. */
quiet = (argv && argv[0] && !strcmp (argv[0], "--quiet"));
cpbuf_in = xtrymalloc (sizeof *cpbuf_in);
if (cpbuf_in == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
copy_buffer_init (cpbuf_in);
cpbuf_out = xtrymalloc (sizeof *cpbuf_out);
if (cpbuf_out == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
copy_buffer_init (cpbuf_out);
cpbuf_extra = xtrymalloc (sizeof *cpbuf_extra);
if (cpbuf_extra == NULL)
{
err = my_error_from_syserror ();
goto leave;
}
copy_buffer_init (cpbuf_extra);
fderrstate.pgmname = pgmname;
fderrstate.quiet = quiet;
fderrstate.status_cb = status_cb;
fderrstate.status_cb_value = status_cb_value;
fderrstate.buffer_size = 256;
fderrstate.buffer = xtrymalloc (fderrstate.buffer_size);
if (!fderrstate.buffer)
{
err = my_error_from_syserror ();
goto leave;
}
if (inextra)
{
err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1);
if (err)
{
log_error ("error creating outbound pipe for extra fp: %s\n",
gpg_strerror (err));
goto leave;
}
exceptclose[0] = extrapipe[0]; /* Do not close in child. */
exceptclose[1] = -1;
/* Now find the argument marker and replace by the pipe's fd.
Yeah, that is an ugly non-thread safe hack but it safes us to
create a copy of the array. */
#ifdef HAVE_W32_SYSTEM
snprintf (extrafdbuf, sizeof extrafdbuf, "-&%lu",
(unsigned long)(void*)_get_osfhandle (extrapipe[0]));
#else
snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]);
#endif
for (argsaveidx=0; argv[argsaveidx]; argsaveidx++)
if (!strcmp (argv[argsaveidx], "-&@INEXTRA@"))
{
argsave = argv[argsaveidx];
argv[argsaveidx] = extrafdbuf;
break;
}
}
else
exceptclose[0] = -1;
err = gnupg_spawn_process (pgmname, argv,
exceptclose, NULL, GNUPG_SPAWN_NONBLOCK,
input? &infp : NULL,
&outfp, &errfp, &pid);
if (extrapipe[0] != -1)
close (extrapipe[0]);
if (argsave)
argv[argsaveidx] = argsave;
if (err)
{
if (!quiet)
log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
goto leave;
}
fds[0].stream = infp;
fds[0].want_write = 1;
if (!input)
fds[0].ignore = 1;
fds[1].stream = outfp;
fds[1].want_read = 1;
fds[2].stream = errfp;
fds[2].want_read = 1;
fds[3].stream = extrafp;
fds[3].want_write = 1;
if (!inextra)
fds[3].ignore = 1;
/* Now read as long as we have something to poll. We continue
reading even after EOF or error on stdout so that we get the
other error messages or remaining output. */
while (! (fds[1].ignore && fds[2].ignore))
{
count = es_poll (fds, DIM(fds), -1);
if (count == -1)
{
err = my_error_from_syserror ();
log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err));
goto leave;
}
if (!count)
{
log_debug ("unexpected timeout while polling '%s'\n", pgmname);
break;
}
if (fds[0].got_write)
{
err = copy_buffer_do_copy (cpbuf_in, input, fds[0].stream);
if (err)
{
log_error ("error feeding data to '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
if (es_feof (input))
{
err = copy_buffer_flush (cpbuf_in, fds[0].stream);
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
continue; /* Retry next time. */
if (err)
{
log_error ("error feeding data to '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
fds[0].ignore = 1; /* ready. */
es_fclose (infp); infp = NULL;
}
}
if (fds[3].got_write)
{
log_assert (inextra);
err = copy_buffer_do_copy (cpbuf_extra, inextra, fds[3].stream);
if (err)
{
log_error ("error feeding data to '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
if (es_feof (inextra))
{
err = copy_buffer_flush (cpbuf_extra, fds[3].stream);
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
continue; /* Retry next time. */
if (err)
{
log_error ("error feeding data to '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
fds[3].ignore = 1; /* ready. */
es_fclose (extrafp); extrafp = NULL;
}
}
if (fds[1].got_read)
{
err = copy_buffer_do_copy (cpbuf_out, fds[1].stream, output);
if (err)
{
log_error ("error reading data from '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
if (es_feof (fds[1].stream))
{
err = copy_buffer_flush (cpbuf_out, output);
if (err)
{
log_error ("error reading data from '%s': %s\n",
pgmname, gpg_strerror (err));
goto leave;
}
fds[1].ignore = 1; /* ready. */
}
}
if (fds[2].got_read)
read_and_log_stderr (&fderrstate, fds + 2);
}
read_and_log_stderr (&fderrstate, NULL); /* Flush. */
es_fclose (infp); infp = NULL;
es_fclose (extrafp); extrafp = NULL;
es_fclose (outfp); outfp = NULL;
es_fclose (errfp); errfp = NULL;
err = gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL);
pid = (pid_t)(-1);
leave:
if (err && pid != (pid_t) -1)
gnupg_kill_process (pid);
es_fclose (infp);
es_fclose (extrafp);
es_fclose (outfp);
es_fclose (errfp);
if (pid != (pid_t)(-1))
gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL);
gnupg_release_process (pid);
copy_buffer_shred (cpbuf_in);
xfree (cpbuf_in);
copy_buffer_shred (cpbuf_out);
xfree (cpbuf_out);
copy_buffer_shred (cpbuf_extra);
xfree (cpbuf_extra);
xfree (fderrstate.buffer);
return err;
}
/* A dummy free function to pass to 'es_mopen'. */
static void
nop_free (void *ptr)
{
(void) ptr;
}
/* Run the program PGMNAME with the command line arguments given in
the NULL terminates array ARGV. If INPUT_STRING is not NULL it
will be fed to stdin of the process. stderr is logged using
- log_info and the process' stdout is returned in a newly malloced
+ log_info and the process's stdout is returned in a newly malloced
buffer RESULT with the length stored at RESULTLEN if not given as
NULL. A hidden Nul is appended to the output. On error NULL is
stored at RESULT, a diagnostic is printed, and an error code
returned. */
gpg_error_t
gnupg_exec_tool (const char *pgmname, const char *argv[],
const char *input_string,
char **result, size_t *resultlen)
{
gpg_error_t err;
estream_t input = NULL;
estream_t output;
size_t len;
size_t nread;
*result = NULL;
if (resultlen)
*resultlen = 0;
if (input_string)
{
len = strlen (input_string);
input = es_mopen ((char *) input_string, len, len,
0 /* don't grow */, NULL, nop_free, "rb");
if (! input)
return my_error_from_syserror ();
}
output = es_fopenmem (0, "wb");
if (! output)
{
err = my_error_from_syserror ();
goto leave;
}
err = gnupg_exec_tool_stream (pgmname, argv, input, NULL, output, NULL, NULL);
if (err)
goto leave;
len = es_ftello (output);
err = es_fseek (output, 0, SEEK_SET);
if (err)
goto leave;
*result = xtrymalloc (len + 1);
if (!*result)
{
err = my_error_from_syserror ();
goto leave;
}
if (len)
{
if (es_read (output, *result, len, &nread))
{
err = my_error_from_syserror ();
goto leave;
}
if (nread != len)
log_fatal ("%s: short read from memstream\n", __func__);
}
(*result)[len] = 0;
if (resultlen)
*resultlen = len;
leave:
es_fclose (input);
es_fclose (output);
if (err)
{
xfree (*result);
*result = NULL;
}
return err;
}
diff --git a/common/exectool.h b/common/exectool.h
index 27bbfc9e5..108903f21 100644
--- a/common/exectool.h
+++ b/common/exectool.h
@@ -1,69 +1,69 @@
/* sh-exectool.h - Utility functions to execute a helper tool
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_EXECTOOL_H
#define GNUPG_COMMON_EXECTOOL_H
#include <gpg-error.h>
/* This callback can be used to process --status-fd outputs of GnuPG
* tools. OPAQUE can be used to communicate between the caller of the
* function and the callback. KEYWORD is the status keyword (see
* doc/DETAILS); it is never NULL. ARGS are the arguments of the
* status line and will also never be NULL; the caller may modify this
* string. */
typedef void (*exec_tool_status_cb_t) (void *opaque,
const char *keyword,
char *args);
/* Run the program PGMNAME with the command line arguments given in
the NULL terminates array ARGV. If INPUT_STRING is not NULL it
will be fed to stdin of the process. stderr is logged using
- log_info and the process' stdout is returned in a newly malloced
+ log_info and the process's stdout is returned in a newly malloced
buffer RESULT with the length stored at RESULTLEN if not given as
NULL. A hidden Nul is appended to the output. On error NULL is
stored at RESULT, a diagnostic is printed, and an error code
returned. */
gpg_error_t gnupg_exec_tool (const char *pgmname, const char *argv[],
const char *input_string,
char **result, size_t *resultlen);
/* Run the program PGMNAME with the command line arguments given in
the NULL terminates array ARGV. If INPUT is not NULL it will be
fed to stdin of the process. stderr is logged using log_info and
- the process' stdout is written to OUTPUT. On error a diagnostic is
+ the process's stdout is written to OUTPUT. On error a diagnostic is
printed, and an error code returned. INEXTRA is reserved. */
gpg_error_t gnupg_exec_tool_stream (const char *pgmname, const char *argv[],
estream_t input, estream_t inextra,
estream_t output,
exec_tool_status_cb_t status_cb,
void *status_cb_value);
#endif /* GNUPG_COMMON_EXECTOOL_H */
diff --git a/common/homedir.c b/common/homedir.c
index 61d3edfb4..7265965e1 100644
--- a/common/homedir.c
+++ b/common/homedir.c
@@ -1,1183 +1,1183 @@
/* homedir.c - Setup the home directory.
* Copyright (C) 2004, 2006, 2007, 2010 Free Software Foundation, Inc.
* Copyright (C) 2013, 2016 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
#include <winsock2.h> /* Due to the stupid mingw64 requirement to
include this header before windows.h which
is often implicitly included. */
#include <shlobj.h>
#ifndef CSIDL_APPDATA
#define CSIDL_APPDATA 0x001a
#endif
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
#ifndef CSIDL_COMMON_APPDATA
#define CSIDL_COMMON_APPDATA 0x0023
#endif
#ifndef CSIDL_FLAG_CREATE
#define CSIDL_FLAG_CREATE 0x8000
#endif
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_STAT
#include <sys/stat.h> /* for stat() */
#endif
#include "util.h"
#include "sysutils.h"
#include "zb32.h"
/* The GnuPG homedir. This is only accessed by the functions
* gnupg_homedir and gnupg_set_homedir. Malloced. */
static char *the_gnupg_homedir;
/* Flag indicating that home directory is not the default one. */
static byte non_default_homedir;
#ifdef HAVE_W32_SYSTEM
/* A flag used to indicate that a control file for gpgconf has been
detected. Under Windows the presence of this file indicates a
portable installations and triggers several changes:
- The GNUGHOME directory is fixed relative to installation
directory. All other means to set the home directory are ignore.
- All registry variables will be ignored.
This flag is not used on Unix systems.
*/
static byte w32_portable_app;
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
-/* This flag is true if this process' binary has been installed under
+/* This flag is true if this process's binary has been installed under
bin and not in the root directory as often used before GnuPG 2.1. */
static byte w32_bin_is_bin;
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
static const char *w32_rootdir (void);
#endif
#ifdef HAVE_W32_SYSTEM
static void
w32_try_mkdir (const char *dir)
{
#ifdef HAVE_W32CE_SYSTEM
wchar_t *wdir = utf8_to_wchar (dir);
if (wdir)
{
CreateDirectory (wdir, NULL);
xfree (wdir);
}
#else
CreateDirectory (dir, NULL);
#endif
}
#endif
/* This is a helper function to load a Windows function from either of
one DLLs. */
#ifdef HAVE_W32_SYSTEM
static HRESULT
w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
{
static int initialized;
static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
if (!initialized)
{
static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
void *handle;
int i;
initialized = 1;
for (i=0, handle = NULL; !handle && dllnames[i]; i++)
{
handle = dlopen (dllnames[i], RTLD_LAZY);
if (handle)
{
func = dlsym (handle, "SHGetFolderPathA");
if (!func)
{
dlclose (handle);
handle = NULL;
}
}
}
}
if (func)
return func (a,b,c,d,e);
else
return -1;
}
#endif /*HAVE_W32_SYSTEM*/
/* Check whether DIR is the default homedir. */
static int
is_gnupg_default_homedir (const char *dir)
{
int result;
char *a = make_absfilename (dir, NULL);
char *b = make_absfilename (GNUPG_DEFAULT_HOMEDIR, NULL);
result = !compare_filenames (a, b);
xfree (b);
xfree (a);
return result;
}
/* Helper to remove trailing slashes from NEWDIR. Return a new
* allocated string if that has been done or NULL if there are no
* slashes to remove. Also inserts a missing slash after a Windows
* drive letter. */
static char *
copy_dir_with_fixup (const char *newdir)
{
char *result = NULL;
char *p;
if (!*newdir)
return NULL;
#ifdef HAVE_W32_SYSTEM
if (newdir[0] && newdir[1] == ':'
&& !(newdir[2] == '/' || newdir[2] == '\\'))
{
/* Drive letter with missing leading slash. */
p = result = xmalloc (strlen (newdir) + 1 + 1);
*p++ = newdir[0];
*p++ = newdir[1];
*p++ = '\\';
strcpy (p, newdir+2);
/* Remove trailing slashes. */
p = result + strlen (result) - 1;
while (p > result+2 && (*p == '/' || *p == '\\'))
*p-- = 0;
}
else if (newdir[strlen (newdir)-1] == '/'
|| newdir[strlen (newdir)-1] == '\\' )
{
result = xstrdup (newdir);
p = result + strlen (result) - 1;
while (p > result
&& (*p == '/' || *p == '\\')
&& (p-1 > result && p[-1] != ':')) /* We keep "c:/". */
*p-- = 0;
}
#else /*!HAVE_W32_SYSTEM*/
if (newdir[strlen (newdir)-1] == '/')
{
result = xstrdup (newdir);
p = result + strlen (result) - 1;
while (p > result && *p == '/')
*p-- = 0;
}
#endif /*!HAVE_W32_SYSTEM*/
return result;
}
/* Get the standard home directory. In general this function should
not be used as it does not consider a registry value (under W32) or
the GNUPGHOME environment variable. It is better to use
default_homedir(). */
const char *
standard_homedir (void)
{
#ifdef HAVE_W32_SYSTEM
static const char *dir;
if (!dir)
{
const char *rdir;
rdir = w32_rootdir ();
if (w32_portable_app)
{
dir = xstrconcat (rdir, DIRSEP_S "home", NULL);
}
else
{
char path[MAX_PATH];
/* It might be better to use LOCAL_APPDATA because this is
defined as "non roaming" and thus more likely to be kept
locally. For private keys this is desired. However,
given that many users copy private keys anyway forth and
back, using a system roaming services might be better
than to let them do it manually. A security conscious
user will anyway use the registry entry to have better
control. */
if (w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
NULL, 0, path) >= 0)
{
char *tmp = xmalloc (strlen (path) + 6 +1);
strcpy (stpcpy (tmp, path), "\\gnupg");
dir = tmp;
/* Try to create the directory if it does not yet exists. */
if (access (dir, F_OK))
w32_try_mkdir (dir);
}
else
dir = GNUPG_DEFAULT_HOMEDIR;
}
}
return dir;
#else/*!HAVE_W32_SYSTEM*/
return GNUPG_DEFAULT_HOMEDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Set up the default home directory. The usual --homedir option
should be parsed later. */
const char *
default_homedir (void)
{
const char *dir;
#ifdef HAVE_W32_SYSTEM
/* For a portable application we only use the standard homedir. */
w32_rootdir ();
if (w32_portable_app)
return standard_homedir ();
#endif /*HAVE_W32_SYSTEM*/
dir = getenv ("GNUPGHOME");
#ifdef HAVE_W32_SYSTEM
if (!dir || !*dir)
{
static const char *saved_dir;
if (!saved_dir)
{
if (!dir || !*dir)
{
char *tmp, *p;
/* This is deprecated; gpgconf --list-dirs prints a
* warning if the homedir has been taken from the
* registry. */
tmp = read_w32_registry_string (NULL,
GNUPG_REGISTRY_DIR,
"HomeDir");
if (tmp && !*tmp)
{
xfree (tmp);
tmp = NULL;
}
if (tmp)
{
/* Strip trailing backslashes. */
p = tmp + strlen (tmp) - 1;
while (p > tmp && *p == '\\')
*p-- = 0;
saved_dir = tmp;
}
}
if (!saved_dir)
saved_dir = standard_homedir ();
}
dir = saved_dir;
}
#endif /*HAVE_W32_SYSTEM*/
if (!dir || !*dir)
dir = GNUPG_DEFAULT_HOMEDIR;
else
{
char *p;
p = copy_dir_with_fixup (dir);
if (p)
dir = p;
if (!is_gnupg_default_homedir (dir))
non_default_homedir = 1;
}
return dir;
}
#ifdef HAVE_W32_SYSTEM
/* Check whether gpgconf is installed and if so read the gpgconf.ctl
file. */
static void
check_portable_app (const char *dir)
{
char *fname;
fname = xstrconcat (dir, DIRSEP_S "gpgconf.exe", NULL);
if (!access (fname, F_OK))
{
strcpy (fname + strlen (fname) - 3, "ctl");
if (!access (fname, F_OK))
{
/* gpgconf.ctl file found. Record this fact. */
w32_portable_app = 1;
{
unsigned int flags;
log_get_prefix (&flags);
log_set_prefix (NULL, (flags | GPGRT_LOG_NO_REGISTRY));
}
/* FIXME: We should read the file to detect special flags
and print a warning if we don't understand them */
}
}
xfree (fname);
}
/* Determine the root directory of the gnupg installation on Windows. */
static const char *
w32_rootdir (void)
{
static int got_dir;
static char dir[MAX_PATH+5];
if (!got_dir)
{
char *p;
int rc;
wchar_t wdir [MAX_PATH+5];
rc = GetModuleFileNameW (NULL, wdir, MAX_PATH);
if (rc && WideCharToMultiByte (CP_UTF8, 0, wdir, -1, dir, MAX_PATH-4,
NULL, NULL) < 0)
rc = 0;
if (!rc)
{
log_debug ("GetModuleFileName failed: %s\n", w32_strerror (-1));
*dir = 0;
}
got_dir = 1;
p = strrchr (dir, DIRSEP_C);
if (p)
{
*p = 0;
check_portable_app (dir);
/* If we are installed below "bin" we strip that and use
the top directory instead. */
p = strrchr (dir, DIRSEP_C);
if (p && !strcmp (p+1, "bin"))
{
*p = 0;
w32_bin_is_bin = 1;
}
}
if (!p)
{
log_debug ("bad filename '%s' returned for this process\n", dir);
*dir = 0;
}
}
if (*dir)
return dir;
/* Fallback to the hardwired value. */
return GNUPG_LIBEXECDIR;
}
static const char *
w32_commondir (void)
{
static char *dir;
if (!dir)
{
const char *rdir;
char path[MAX_PATH];
/* Make sure that w32_rootdir has been called so that we are
able to check the portable application flag. The common dir
is the identical to the rootdir. In that case there is also
no need to strdup its value. */
rdir = w32_rootdir ();
if (w32_portable_app)
return rdir;
if (w32_shgetfolderpath (NULL, CSIDL_COMMON_APPDATA,
NULL, 0, path) >= 0)
{
char *tmp = xmalloc (strlen (path) + 4 +1);
strcpy (stpcpy (tmp, path), "\\GNU");
dir = tmp;
/* No auto create of the directory. Either the installer or
the admin has to create these directories. */
}
else
{
/* Ooops: Not defined - probably an old Windows version.
Use the installation directory instead. */
dir = xstrdup (rdir);
}
}
return dir;
}
#endif /*HAVE_W32_SYSTEM*/
/* Change the homedir. Some care must be taken to set this early
* enough because previous calls to gnupg_homedir may else return a
* different string. */
void
gnupg_set_homedir (const char *newdir)
{
char *tmp = NULL;
if (!newdir || !*newdir)
newdir = default_homedir ();
else
{
tmp = copy_dir_with_fixup (newdir);
if (tmp)
newdir = tmp;
if (!is_gnupg_default_homedir (newdir))
non_default_homedir = 1;
}
xfree (the_gnupg_homedir);
the_gnupg_homedir = make_absfilename (newdir, NULL);;
xfree (tmp);
}
/* Return the homedir. The returned string is valid until another
* gnupg-set-homedir call. This is always an absolute directory name.
* The function replaces the former global var opt.homedir. */
const char *
gnupg_homedir (void)
{
/* If a homedir has not been set, set it to the default. */
if (!the_gnupg_homedir)
the_gnupg_homedir = make_absfilename (default_homedir (), NULL);
return the_gnupg_homedir;
}
/* Return whether the home dir is the default one. */
int
gnupg_default_homedir_p (void)
{
return !non_default_homedir;
}
/* Return the directory name used by daemons for their current working
* directory. */
const char *
gnupg_daemon_rootdir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
{
char path[MAX_PATH];
size_t n;
n = GetSystemDirectoryA (path, sizeof path);
if (!n || n >= sizeof path)
name = xstrdup ("/"); /* Error - use the curret top dir instead. */
else
name = xstrdup (path);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
return "/";
#endif /*!HAVE_W32_SYSTEM*/
}
/* Helper for gnupg-socketdir. This is a global function, so that
* gpgconf can use it for its --create-socketdir command. If
* SKIP_CHECKS is set permission checks etc. are not done. The
* function always returns a malloced directory name and stores these
* bit flags at R_INFO:
*
* 1 := Internal error, stat failed, out of core, etc.
* 2 := No /run/user directory.
* 4 := Directory not owned by the user, not a directory
* or wrong permissions.
* 8 := Same as 4 but for the subdir.
* 16 := mkdir failed
* 32 := Non default homedir; checking subdir.
* 64 := Subdir does not exist.
* 128 := Using homedir as fallback.
*/
char *
_gnupg_socketdir_internal (int skip_checks, unsigned *r_info)
{
#if defined(HAVE_W32_SYSTEM) || !defined(HAVE_STAT)
char *name;
(void)skip_checks;
*r_info = 0;
name = xstrdup (gnupg_homedir ());
#else /* Unix and stat(2) available. */
static const char * const bases[] = {
#ifdef USE_RUN_GNUPG_USER_SOCKET
"/run/gnupg",
#endif
"/run",
#ifdef USE_RUN_GNUPG_USER_SOCKET
"/var/run/gnupg",
#endif
"/var/run",
NULL
};
int i;
struct stat sb;
char prefix[19 + 1 + 20 + 6 + 1];
const char *s;
char *name = NULL;
*r_info = 0;
/* First make sure that non_default_homedir can be set. */
gnupg_homedir ();
/* It has been suggested to first check XDG_RUNTIME_DIR envvar.
* However, the specs state that the lifetime of the directory MUST
* be bound to the user being logged in. Now GnuPG may also be run
* as a background process with no (desktop) user logged in. Thus
* we better don't do that. */
/* Check whether we have a /run/[gnupg/]user dir. */
for (i=0; bases[i]; i++)
{
snprintf (prefix, sizeof prefix, "%s/user/%u",
bases[i], (unsigned int)getuid ());
if (!stat (prefix, &sb) && S_ISDIR(sb.st_mode))
break;
}
if (!bases[i])
{
*r_info |= 2; /* No /run/user directory. */
goto leave;
}
if (sb.st_uid != getuid ())
{
*r_info |= 4; /* Not owned by the user. */
if (!skip_checks)
goto leave;
}
if (strlen (prefix) + 7 >= sizeof prefix)
{
*r_info |= 1; /* Ooops: Buffer too short to append "/gnupg". */
goto leave;
}
strcat (prefix, "/gnupg");
/* Check whether the gnupg sub directory has proper permissions. */
if (stat (prefix, &sb))
{
if (errno != ENOENT)
{
*r_info |= 1; /* stat failed. */
goto leave;
}
/* Try to create the directory and check again. */
if (gnupg_mkdir (prefix, "-rwx"))
{
*r_info |= 16; /* mkdir failed. */
goto leave;
}
if (stat (prefix, &sb))
{
*r_info |= 1; /* stat failed. */
goto leave;
}
}
/* Check that it is a directory, owned by the user, and only the
* user has permissions to use it. */
if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 4; /* Bad permissions or not a directory. */
if (!skip_checks)
goto leave;
}
/* If a non default homedir is used, we check whether an
* corresponding sub directory below the socket dir is available
* and use that. We hash the non default homedir to keep the new
* subdir short enough. */
if (non_default_homedir)
{
char sha1buf[20];
char *suffix;
*r_info |= 32; /* Testing subdir. */
s = gnupg_homedir ();
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, s, strlen (s));
suffix = zb32_encode (sha1buf, 8*15);
if (!suffix)
{
*r_info |= 1; /* Out of core etc. */
goto leave;
}
name = strconcat (prefix, "/d.", suffix, NULL);
xfree (suffix);
if (!name)
{
*r_info |= 1; /* Out of core etc. */
goto leave;
}
/* Stat that directory and check constraints.
* The command
* gpgconf --remove-socketdir
* can be used to remove that directory. */
if (stat (name, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else if (!skip_checks)
{
/* Try to create the directory and check again. */
if (gnupg_mkdir (name, "-rwx"))
*r_info |= 16; /* mkdir failed. */
else if (stat (prefix, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else
*r_info |= 64; /* Subdir does not exist. */
}
else
goto leave; /* Success! */
}
else
*r_info |= 64; /* Subdir does not exist. */
if (!skip_checks)
{
xfree (name);
name = NULL;
goto leave;
}
}
else if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 8; /* Bad permissions or subdir is not a directory. */
if (!skip_checks)
{
xfree (name);
name = NULL;
goto leave;
}
}
}
else
name = xstrdup (prefix);
leave:
/* If nothing works fall back to the homedir. */
if (!name)
{
*r_info |= 128; /* Fallback. */
name = xstrdup (gnupg_homedir ());
}
#endif /* Unix */
return name;
}
/*
* Return the name of the socket dir. That is the directory used for
* the IPC local sockets. This is an absolute directory name.
*/
const char *
gnupg_socketdir (void)
{
static char *name;
if (!name)
{
unsigned int dummy;
name = _gnupg_socketdir_internal (0, &dummy);
}
return name;
}
/* Return the name of the sysconfdir. This is a static string. This
function is required because under Windows we can't simply compile
it in. */
const char *
gnupg_sysconfdir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
{
const char *s1, *s2;
s1 = w32_commondir ();
s2 = DIRSEP_S "etc" DIRSEP_S "gnupg";
name = xmalloc (strlen (s1) + strlen (s2) + 1);
strcpy (stpcpy (name, s1), s2);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_SYSCONFDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_bindir (void)
{
#if defined (HAVE_W32CE_SYSTEM)
static char *name;
if (!name)
name = xstrconcat (w32_rootdir (), DIRSEP_S "bin", NULL);
return name;
#elif defined(HAVE_W32_SYSTEM)
const char *rdir;
rdir = w32_rootdir ();
if (w32_bin_is_bin)
{
static char *name;
if (!name)
name = xstrconcat (rdir, DIRSEP_S "bin", NULL);
return name;
}
else
return rdir;
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_BINDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Return the name of the libexec directory. The name is allocated in
a static area on the first use. This function won't fail. */
const char *
gnupg_libexecdir (void)
{
#ifdef HAVE_W32_SYSTEM
return gnupg_bindir ();
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_LIBEXECDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_libdir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
name = xstrconcat (w32_rootdir (), DIRSEP_S "lib" DIRSEP_S "gnupg", NULL);
return name;
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_LIBDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_datadir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "gnupg", NULL);
return name;
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_DATADIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_localedir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "locale",
NULL);
return name;
#else /*!HAVE_W32_SYSTEM*/
return LOCALEDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Return the name of the cache directory. The name is allocated in a
static area on the first use. Windows only: If the directory does
not exist it is created. */
const char *
gnupg_cachedir (void)
{
#ifdef HAVE_W32_SYSTEM
static const char *dir;
if (!dir)
{
const char *rdir;
rdir = w32_rootdir ();
if (w32_portable_app)
{
dir = xstrconcat (rdir,
DIRSEP_S, "var",
DIRSEP_S, "cache",
DIRSEP_S, "gnupg", NULL);
}
else
{
char path[MAX_PATH];
const char *s1[] = { "GNU", "cache", "gnupg", NULL };
int s1_len;
const char **comp;
s1_len = 0;
for (comp = s1; *comp; comp++)
s1_len += 1 + strlen (*comp);
if (w32_shgetfolderpath (NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
NULL, 0, path) >= 0)
{
char *tmp = xmalloc (strlen (path) + s1_len + 1);
char *p;
p = stpcpy (tmp, path);
for (comp = s1; *comp; comp++)
{
p = stpcpy (p, "\\");
p = stpcpy (p, *comp);
if (access (tmp, F_OK))
w32_try_mkdir (tmp);
}
dir = tmp;
}
else
{
dir = "c:\\temp\\cache\\gnupg";
#ifdef HAVE_W32CE_SYSTEM
dir += 2;
w32_try_mkdir ("\\temp\\cache");
w32_try_mkdir ("\\temp\\cache\\gnupg");
#endif
}
}
}
return dir;
#else /*!HAVE_W32_SYSTEM*/
return GNUPG_LOCALSTATEDIR "/cache/" PACKAGE_NAME;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Return the standard socket name used by gpg-agent. */
const char *
gpg_agent_socket_name (void)
{
static char *name;
if (!name)
name = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
return name;
}
/* Return the user socket name used by DirMngr. */
const char *
dirmngr_socket_name (void)
{
static char *name;
if (!name)
name = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL);
return name;
}
/* Return the default pinentry name. If RESET is true the internal
cache is first flushed. */
static const char *
get_default_pinentry_name (int reset)
{
static struct {
const char *(*rfnc)(void);
const char *name;
} names[] = {
/* The first entry is what we return in case we found no
other pinentry. */
{ gnupg_bindir, DIRSEP_S "pinentry" EXEEXT_S },
#ifdef HAVE_W32_SYSTEM
/* Try Gpg4win directory (with bin and without.) */
{ w32_rootdir, "\\..\\Gpg4win\\bin\\pinentry.exe" },
{ w32_rootdir, "\\..\\Gpg4win\\pinentry.exe" },
/* Try old Gpgwin directory. */
{ w32_rootdir, "\\..\\GNU\\GnuPG\\pinentry.exe" },
/* Try a Pinentry from the common GNU dir. */
{ w32_rootdir, "\\..\\GNU\\bin\\pinentry.exe" },
#endif
/* Last chance is a pinentry-basic (which comes with the
GnuPG 2.1 Windows installer). */
{ gnupg_bindir, DIRSEP_S "pinentry-basic" EXEEXT_S }
};
static char *name;
if (reset)
{
xfree (name);
name = NULL;
}
if (!name)
{
int i;
for (i=0; i < DIM(names); i++)
{
char *name2;
name2 = xstrconcat (names[i].rfnc (), names[i].name, NULL);
if (!access (name2, F_OK))
{
/* Use that pinentry. */
xfree (name);
name = name2;
break;
}
if (!i) /* Store the first as fallback return. */
name = name2;
else
xfree (name2);
}
}
return name;
}
/* If set, 'gnupg_module_name' returns modules from that build
* directory. */
static char *gnupg_build_directory;
/* For sanity checks. */
static int gnupg_module_name_called;
/* Set NEWDIR as the new build directory. This will make
* 'gnupg_module_name' return modules from that build directory. Must
* be called before any invocation of 'gnupg_module_name', and must
* not be called twice. It can be used by test suites to make sure
* the components from the build directory are used instead of
* potentially outdated installed ones. */
void
gnupg_set_builddir (const char *newdir)
{
log_assert (! gnupg_module_name_called);
log_assert (! gnupg_build_directory);
gnupg_build_directory = xtrystrdup (newdir);
}
/* If no build directory has been configured, try to set it from the
* environment. We only do this in development builds to avoid
* increasing the set of influential environment variables and hence
* the attack surface of production builds. */
static void
gnupg_set_builddir_from_env (void)
{
#if defined(IS_DEVELOPMENT_VERSION) || defined(ENABLE_GNUPG_BUILDDIR_ENVVAR)
if (gnupg_build_directory)
return;
gnupg_build_directory = getenv ("GNUPG_BUILDDIR");
#endif
}
/* Return the file name of a helper tool. WHICH is one of the
GNUPG_MODULE_NAME_foo constants. */
const char *
gnupg_module_name (int which)
{
gnupg_set_builddir_from_env ();
gnupg_module_name_called = 1;
#define X(a,b,c) do { \
static char *name; \
if (!name) \
name = gnupg_build_directory \
? xstrconcat (gnupg_build_directory, \
DIRSEP_S b DIRSEP_S c EXEEXT_S, NULL) \
: xstrconcat (gnupg_ ## a (), DIRSEP_S c EXEEXT_S, NULL); \
return name; \
} while (0)
switch (which)
{
case GNUPG_MODULE_NAME_AGENT:
#ifdef GNUPG_DEFAULT_AGENT
return GNUPG_DEFAULT_AGENT;
#else
X(bindir, "agent", "gpg-agent");
#endif
case GNUPG_MODULE_NAME_PINENTRY:
#ifdef GNUPG_DEFAULT_PINENTRY
return GNUPG_DEFAULT_PINENTRY; /* (Set by a configure option) */
#else
return get_default_pinentry_name (0);
#endif
case GNUPG_MODULE_NAME_SCDAEMON:
#ifdef GNUPG_DEFAULT_SCDAEMON
return GNUPG_DEFAULT_SCDAEMON;
#else
X(libexecdir, "scd", "scdaemon");
#endif
case GNUPG_MODULE_NAME_DIRMNGR:
#ifdef GNUPG_DEFAULT_DIRMNGR
return GNUPG_DEFAULT_DIRMNGR;
#else
X(bindir, "dirmngr", DIRMNGR_NAME);
#endif
case GNUPG_MODULE_NAME_KEYBOXD:
#ifdef GNUPG_DEFAULT_KEYBOXD
return GNUPG_DEFAULT_KEYBOXD;
#else
X(bindir, "keyboxd", KEYBOXD_NAME);
#endif
case GNUPG_MODULE_NAME_PROTECT_TOOL:
#ifdef GNUPG_DEFAULT_PROTECT_TOOL
return GNUPG_DEFAULT_PROTECT_TOOL;
#else
X(libexecdir, "agent", "gpg-protect-tool");
#endif
case GNUPG_MODULE_NAME_DIRMNGR_LDAP:
#ifdef GNUPG_DEFAULT_DIRMNGR_LDAP
return GNUPG_DEFAULT_DIRMNGR_LDAP;
#else
X(libexecdir, "dirmngr", "dirmngr_ldap");
#endif
case GNUPG_MODULE_NAME_CHECK_PATTERN:
X(libexecdir, "tools", "gpg-check-pattern");
case GNUPG_MODULE_NAME_GPGSM:
X(bindir, "sm", "gpgsm");
case GNUPG_MODULE_NAME_GPG:
#if USE_GPG2_HACK
if (! gnupg_build_directory)
X(bindir, "g10", GPG_NAME "2");
else
#endif
X(bindir, "g10", GPG_NAME);
case GNUPG_MODULE_NAME_GPGV:
#if USE_GPG2_HACK
if (! gnupg_build_directory)
X(bindir, "g10", GPG_NAME "v2");
else
#endif
X(bindir, "g10", GPG_NAME "v");
case GNUPG_MODULE_NAME_CONNECT_AGENT:
X(bindir, "tools", "gpg-connect-agent");
case GNUPG_MODULE_NAME_GPGCONF:
X(bindir, "tools", "gpgconf");
default:
BUG ();
}
#undef X
}
/* Flush some of the cached module names. This is for example used by
gpg-agent to allow configuring a different pinentry. */
void
gnupg_module_name_flush_some (void)
{
(void)get_default_pinentry_name (1);
}
diff --git a/common/percent.c b/common/percent.c
index ecc6a1959..debf15784 100644
--- a/common/percent.c
+++ b/common/percent.c
@@ -1,321 +1,321 @@
/* percent.c - Percent escaping
* Copyright (C) 2008, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include "util.h"
/* Create a newly alloced string from STRING with all spaces and
* control characters converted to plus signs or %xx sequences. The
* function returns the new string or NULL in case of a malloc
* failure.
*
- * Note that this fucntion also escapes the quote character to work
+ * Note that this function also escapes the quote character to work
* around a bug in the mingw32 runtime which does not correctly handle
* command line quoting. We correctly double the quote mark when
* calling a program (i.e. gpg-protect-tool), but the pre-main code
* does not notice the double quote as an escaped quote. We do this
* also on POSIX systems for consistency. */
char *
percent_plus_escape (const char *string)
{
char *buffer, *p;
const char *s;
size_t length;
for (length=1, s=string; *s; s++)
{
if (*s == '+' || *s == '\"' || *s == '%'
|| *(const unsigned char *)s < 0x20)
length += 3;
else
length++;
}
buffer = p = xtrymalloc (length);
if (!buffer)
return NULL;
for (s=string; *s; s++)
{
if (*s == '+' || *s == '\"' || *s == '%'
|| *(const unsigned char *)s < 0x20)
{
snprintf (p, 4, "%%%02X", *(unsigned char *)s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
return buffer;
}
/* Create a newly malloced string from (DATA,DATALEN) with embedded
* nuls quoted as %00. The standard percent unescaping can be used to
* reverse this encoding. With PLUS_ESCAPE set plus-escaping (spaces
* are replaced by a '+') and escaping of characters with values less
* than 0x20 is used. If PREFIX is not NULL it will be prepended to
* the output in standard escape format; that is PLUS_ESCAPING is
* ignored for PREFIX. */
char *
percent_data_escape (int plus_escape, const char *prefix,
const void *data, size_t datalen)
{
char *buffer, *p;
const unsigned char *s;
size_t n;
size_t length = 1;
if (prefix)
{
for (s = prefix; *s; s++)
{
if (*s == '%' || *s < 0x20)
length += 3;
else
length++;
}
}
for (s=data, n=datalen; n; s++, n--)
{
if (!*s || *s == '%' || (plus_escape && (*s < ' ' || *s == '+')))
length += 3;
else
length++;
}
buffer = p = xtrymalloc (length);
if (!buffer)
return NULL;
if (prefix)
{
for (s = prefix; *s; s++)
{
if (*s == '%' || *s < 0x20)
{
snprintf (p, 4, "%%%02X", *s);
p += 3;
}
else
*p++ = *s;
}
}
for (s=data, n=datalen; n; s++, n--)
{
if (!*s)
{
memcpy (p, "%00", 3);
p += 3;
}
else if (*s == '%')
{
memcpy (p, "%25", 3);
p += 3;
}
else if (plus_escape && *s == ' ')
{
*p++ = '+';
}
else if (plus_escape && (*s < ' ' || *s == '+'))
{
snprintf (p, 4, "%%%02X", *s);
p += 3;
}
else
*p++ = *s;
}
*p = 0;
return buffer;
}
/* Do the percent and plus/space unescaping from STRING to BUFFER and
return the length of the valid buffer. Plus unescaping is only
done if WITHPLUS is true. An escaped Nul character will be
replaced by NULREPL. */
static size_t
do_unescape (unsigned char *buffer, const unsigned char *string,
int withplus, int nulrepl)
{
unsigned char *p = buffer;
while (*string)
{
if (*string == '%' && string[1] && string[2])
{
string++;
*p = xtoi_2 (string);
if (!*p)
*p = nulrepl;
string++;
}
else if (*string == '+' && withplus)
*p = ' ';
else
*p = *string;
p++;
string++;
}
return (p - buffer);
}
/* Count space required after unescaping STRING. Note that this will
never be larger than strlen (STRING). */
static size_t
count_unescape (const unsigned char *string)
{
size_t n = 0;
while (*string)
{
if (*string == '%' && string[1] && string[2])
{
string++;
string++;
}
string++;
n++;
}
return n;
}
/* Helper. */
static char *
do_plus_or_plain_unescape (const char *string, int withplus, int nulrepl)
{
size_t nbytes, n;
char *newstring;
nbytes = count_unescape (string);
newstring = xtrymalloc (nbytes+1);
if (newstring)
{
n = do_unescape (newstring, string, withplus, nulrepl);
assert (n == nbytes);
newstring[n] = 0;
}
return newstring;
}
/* Create a new allocated string from STRING with all "%xx" sequences
decoded and all plus signs replaced by a space. Embedded Nul
characters are replaced by the value of NULREPL. The function
returns the new string or NULL in case of a malloc failure. */
char *
percent_plus_unescape (const char *string, int nulrepl)
{
return do_plus_or_plain_unescape (string, 1, nulrepl);
}
/* Create a new allocated string from STRING with all "%xx" sequences
decoded. Embedded Nul characters are replaced by the value of
NULREPL. The function returns the new string or NULL in case of a
malloc failure. */
char *
percent_unescape (const char *string, int nulrepl)
{
return do_plus_or_plain_unescape (string, 0, nulrepl);
}
static size_t
do_unescape_inplace (char *string, int withplus, int nulrepl)
{
unsigned char *p, *p0;
p = p0 = string;
while (*string)
{
if (*string == '%' && string[1] && string[2])
{
string++;
*p = xtoi_2 (string);
if (!*p)
*p = nulrepl;
string++;
}
else if (*string == '+' && withplus)
*p = ' ';
else
*p = *string;
p++;
string++;
}
return (p - p0);
}
/* Perform percent and plus unescaping in STRING and return the new
valid length of the string. Embedded Nul characters are replaced
by the value of NULREPL. A terminating Nul character is not
inserted; the caller might want to call this function this way:
foo[percent_plus_unescape_inplace (foo, 0)] = 0;
*/
size_t
percent_plus_unescape_inplace (char *string, int nulrepl)
{
return do_unescape_inplace (string, 1, nulrepl);
}
/* Perform percent unescaping in STRING and return the new valid
length of the string. Embedded Nul characters are replaced by the
value of NULREPL. A terminating Nul character is not inserted; the
caller might want to call this function this way:
foo[percent_unescape_inplace (foo, 0)] = 0;
*/
size_t
percent_unescape_inplace (char *string, int nulrepl)
{
return do_unescape_inplace (string, 0, nulrepl);
}
diff --git a/common/session-env.c b/common/session-env.c
index c1eb1d959..30241a993 100644
--- a/common/session-env.c
+++ b/common/session-env.c
@@ -1,400 +1,400 @@
/* session-env.c - Session environment helper functions.
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include "util.h"
#include "session-env.h"
struct variable_s
{
char *value; /* Pointer into NAME to the Nul terminated value. */
int is_default; /* The value is a default one. */
char name[1]; /* Nul terminated Name and space for the value. */
};
/* The session environment object. */
struct session_environment_s
{
size_t arraysize; /* Allocated size or ARRAY. */
size_t arrayused; /* Used size of ARRAY. */
struct variable_s **array; /* Array of variables. NULL slots are unused. */
};
/* A list of environment variables we pass from the actual user
(e.g. gpgme) down to the pinentry. We do not handle the locale
settings because they do not only depend on envvars. */
static struct
{
const char *name;
const char *assname; /* Name used by Assuan or NULL. */
} stdenvnames[] = {
{ "GPG_TTY", "ttyname" }, /* GnuPG specific envvar. */
{ "TERM", "ttytype" }, /* Used to set ttytype. */
{ "DISPLAY", "display" }, /* The X-Display. */
{ "XAUTHORITY","xauthority"}, /* Xlib Authentication. */
{ "XMODIFIERS" }, /* Used by Xlib to select X input
modules (eg "@im=SCIM"). */
{ "GTK_IM_MODULE" }, /* Used by gtk to select gtk input
modules (eg "scim-bridge"). */
{ "DBUS_SESSION_BUS_ADDRESS" },/* Used by GNOME3 to talk to gcr over
dbus */
{ "QT_IM_MODULE" }, /* Used by Qt to select qt input
modules (eg "xim"). */
{ "INSIDE_EMACS" }, /* Set by Emacs before running a
process. */
{ "PINENTRY_USER_DATA", "pinentry-user-data"}
/* Used for communication with
non-standard Pinentries. */
};
/* Track last allocated arraysize of all objects ever created. If
nothing has ever been allocated we use INITIAL_ARRAYSIZE and we
will never use more than MAXDEFAULT_ARRAYSIZE for initial
allocation. Note that this is not reentrant if used with a
preemptive thread model. */
static size_t lastallocatedarraysize;
#define INITIAL_ARRAYSIZE 8 /* Let's use the number of stdenvnames. */
#define CHUNK_ARRAYSIZE 10
#define MAXDEFAULT_ARRAYSIZE (INITIAL_ARRAYSIZE + CHUNK_ARRAYSIZE * 5)
/* Return the names of standard environment variables one after the
other. The caller needs to set the value at the address of
ITERATOR initially to 0 and then call this function until it returns
NULL. */
const char *
session_env_list_stdenvnames (int *iterator, const char **r_assname)
{
int idx = *iterator;
if (idx < 0 || idx >= DIM (stdenvnames))
return NULL;
*iterator = idx + 1;
if (r_assname)
*r_assname = stdenvnames[idx].assname;
return stdenvnames[idx].name;
}
/* Create a new session environment object. Return NULL and sets
ERRNO on failure. */
session_env_t
session_env_new (void)
{
session_env_t se;
se = xtrycalloc (1, sizeof *se);
if (se)
{
se->arraysize = (lastallocatedarraysize?
lastallocatedarraysize : INITIAL_ARRAYSIZE);
se->array = xtrycalloc (se->arraysize, sizeof *se->array);
if (!se->array)
{
xfree (se);
se = NULL;
}
}
return se;
}
/* Release a session environment object. */
void
session_env_release (session_env_t se)
{
int idx;
if (!se)
return;
if (se->arraysize > INITIAL_ARRAYSIZE
&& se->arraysize <= MAXDEFAULT_ARRAYSIZE
&& se->arraysize > lastallocatedarraysize)
lastallocatedarraysize = se->arraysize;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx])
xfree (se->array[idx]);
xfree (se->array);
xfree (se);
}
static gpg_error_t
delete_var (session_env_t se, const char *name)
{
int idx;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
xfree (se->array[idx]);
se->array[idx] = NULL;
}
return 0;
}
static gpg_error_t
update_var (session_env_t se, const char *string, size_t namelen,
const char *explicit_value, int set_default)
{
int idx;
int freeidx = -1;
const char *value;
size_t valuelen;
struct variable_s *var;
if (explicit_value)
value = explicit_value;
else
value = string + namelen + 1;
valuelen = strlen (value);
for (idx=0; idx < se->arrayused; idx++)
{
if (!se->array[idx])
freeidx = idx;
else if (!strncmp (se->array[idx]->name, string, namelen)
&& strlen (se->array[idx]->name) == namelen)
{
if (strlen (se->array[idx]->value) == valuelen)
{
/* The new value has the same length. We can update it
in-place. */
memcpy (se->array[idx]->value, value, valuelen);
se->array[idx]->is_default = !!set_default;
return 0;
}
/* Prepare for update. */
freeidx = idx;
}
}
if (freeidx == -1)
{
if (se->arrayused == se->arraysize)
{
/* Reallocate the array. */
size_t newsize;
struct variable_s **newarray;
newsize = se->arraysize + CHUNK_ARRAYSIZE;
newarray = xtrycalloc (newsize, sizeof *newarray);
if (!newarray)
return gpg_error_from_syserror ();
for (idx=0; idx < se->arrayused; idx++)
newarray[idx] = se->array[idx];
se->arraysize = newsize;
xfree (se->array);
se->array = newarray;
}
freeidx = se->arrayused++;
}
/* Allocate new memory and return an error if that didn't worked.
Allocating it first allows us to keep the old value; it doesn't
matter that arrayused has already been incremented in case of a
new entry - it will then pint to a NULL slot. */
var = xtrymalloc (sizeof *var + namelen + 1 + valuelen);
if (!var)
return gpg_error_from_syserror ();
var->is_default = !!set_default;
memcpy (var->name, string, namelen);
var->name[namelen] = '\0';
var->value = var->name + namelen + 1;
strcpy (var->value, value);
xfree (se->array[freeidx]);
se->array[freeidx] = var;
return 0;
}
/* Set or update an environment variable of the session environment.
String is similar to the putval(3) function but it is reentrant and
takes a copy. In particular it exhibits this behaviour:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
On success 0 is returned; on error an gpg-error code. */
gpg_error_t
session_env_putenv (session_env_t se, const char *string)
{
const char *s;
if (!string || !*string)
return gpg_error (GPG_ERR_INV_VALUE);
s = strchr (string, '=');
if (s == string)
return gpg_error (GPG_ERR_INV_VALUE);
if (!s)
return delete_var (se, string);
else
return update_var (se, string, s - string, NULL, 0);
}
/* Same as session_env_putenv but with name and value given as distict
values. */
gpg_error_t
session_env_setenv (session_env_t se, const char *name, const char *value)
{
if (!name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
if (!value)
return delete_var (se, name);
else
return update_var (se, name, strlen (name), value, 0);
}
/* Return the value of the environment variable NAME from the SE
object. If the variable does not exist, NULL is returned. The
returned value is valid as long as SE is valid and as long it has
not been removed or updated by a call to session_env_putenv. The
caller MUST not change the returned value. */
char *
session_env_getenv (session_env_t se, const char *name)
{
int idx;
if (!se || !name || !*name)
return NULL;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
return se->array[idx]->is_default? NULL : se->array[idx]->value;
return NULL;
}
/* Return the value of the environment variable NAME from the SE
object. The returned value is valid as long as SE is valid and as
long it has not been removed or updated by a call to
session_env_putenv. If the variable does not exist, the function
tries to return the value trough a call to getenv; if that returns
a value, this value is recorded and used. If no value could be
found, returns NULL. The caller must not change the returned
value. */
char *
session_env_getenv_or_default (session_env_t se, const char *name,
int *r_default)
{
int idx;
char *defvalue;
if (r_default)
*r_default = 0;
if (!se || !name || !*name)
return NULL;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
if (r_default && se->array[idx]->is_default)
*r_default = 1;
return se->array[idx]->value;
}
/* Get the default value with an additional fallback for GPG_TTY. */
defvalue = getenv (name);
if ((!defvalue || !*defvalue) && !strcmp (name, "GPG_TTY")
&& gnupg_ttyname (0))
{
defvalue = gnupg_ttyname (0);
}
if (defvalue)
{
/* Record the default value for later use so that we are safe
from later modifications of the environment. We need to take
a copy to better cope with the rules of putenv(3). We ignore
the error of the update function because we can't return an
explicit error anyway and the following scan would then fail
anyway. */
update_var (se, name, strlen (name), defvalue, 1);
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
if (r_default && se->array[idx]->is_default)
*r_default = 1;
return se->array[idx]->value;
}
}
return NULL;
}
/* List the entire environment stored in SE. The caller initially
needs to set the value of ITERATOR to 0 and then call this function
until it returns NULL. The value is returned at R_VALUE. If
R_DEFAULT is not NULL, the default flag is stored on return. The
default flag indicates that the value has been taken from the
- process' environment. The caller must not change the returned
+ process's environment. The caller must not change the returned
name or value. */
char *
session_env_listenv (session_env_t se, int *iterator,
const char **r_value, int *r_default)
{
int idx = *iterator;
if (!se || idx < 0)
return NULL;
for (; idx < se->arrayused; idx++)
if (se->array[idx])
{
*iterator = idx+1;
if (r_default)
*r_default = se->array[idx]->is_default;
if (r_value)
*r_value = se->array[idx]->value;
return se->array[idx]->name;
}
return NULL;
}
diff --git a/common/t-name-value.c b/common/t-name-value.c
index 13a383ddb..bd17404ac 100644
--- a/common/t-name-value.c
+++ b/common/t-name-value.c
@@ -1,618 +1,618 @@
/* t-name-value.c - Module test for name-value.c
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "util.h"
#include "name-value.h"
static int verbose;
static int private_key_mode;
static nvc_t
my_nvc_new (void)
{
if (private_key_mode)
return nvc_new_private_key ();
else
return nvc_new ();
}
void
test_getting_values (nvc_t pk)
{
nve_t e;
e = nvc_lookup (pk, "Comment:");
assert (e);
/* Names are case-insensitive. */
e = nvc_lookup (pk, "comment:");
assert (e);
e = nvc_lookup (pk, "COMMENT:");
assert (e);
e = nvc_lookup (pk, "SomeOtherName:");
assert (e);
}
void
test_key_extraction (nvc_t pk)
{
gpg_error_t err;
gcry_sexp_t key;
if (private_key_mode)
{
err = nvc_get_private_key (pk, &key);
assert (err == 0);
assert (key);
if (verbose)
gcry_sexp_dump (key);
gcry_sexp_release (key);
}
else
{
err = nvc_get_private_key (pk, &key);
assert (gpg_err_code (err) == GPG_ERR_MISSING_KEY);
}
}
void
test_iteration (nvc_t pk)
{
int i;
nve_t e;
i = 0;
for (e = nvc_first (pk); e; e = nve_next (e))
i++;
assert (i == 4);
i = 0;
for (e = nvc_lookup (pk, "Comment:");
e;
e = nve_next_value (e, "Comment:"))
i++;
assert (i == 3);
}
void
test_whitespace (nvc_t pk)
{
nve_t e;
e = nvc_lookup (pk, "One:");
assert (e);
assert (strcmp (nve_value (e), "WithoutWhitespace") == 0);
e = nvc_lookup (pk, "Two:");
assert (e);
assert (strcmp (nve_value (e), "With Whitespace") == 0);
e = nvc_lookup (pk, "Three:");
assert (e);
assert (strcmp (nve_value (e),
"Blank lines in continuations encode newlines.\n"
"Next paragraph.") == 0);
}
struct
{
char *value;
void (*test_func) (nvc_t);
} tests[] =
{
{
"# This is a comment followed by an empty line\n"
"\n",
NULL,
},
{
"# This is a comment followed by two empty lines, Windows style\r\n"
"\r\n"
"\r\n",
NULL,
},
{
"# Some name,value pairs\n"
"Comment: Some comment.\n"
"SomeOtherName: Some value.\n",
test_getting_values,
},
{
" # Whitespace is preserved as much as possible\r\n"
"Comment:Some comment.\n"
"SomeOtherName: Some value. \n",
test_getting_values,
},
{
"# Values may be continued in the next line as indicated by leading\n"
"# space\n"
"Comment: Some rather long\n"
" comment that is continued in the next line.\n"
"\n"
" Blank lines with or without whitespace are allowed within\n"
" continuations to allow paragraphs.\n"
"SomeOtherName: Some value.\n",
test_getting_values,
},
{
"# Names may be given multiple times forming an array of values\n"
"Comment: Some comment, element 0.\n"
"Comment: Some comment, element 1.\n"
"Comment: Some comment, element 2.\n"
"SomeOtherName: Some value.\n",
test_iteration,
},
{
"# One whitespace at the beginning of a continuation is swallowed.\n"
"One: Without\n"
" Whitespace\n"
"Two: With\n"
" Whitespace\n"
"Three: Blank lines in continuations encode newlines.\n"
"\n"
" Next paragraph.\n",
test_whitespace,
},
{
"Description: Key to sign all GnuPG released tarballs.\n"
" The key is actually stored on a smart card.\n"
"Use-for-ssh: yes\n"
"OpenSSH-cert: long base64 encoded string wrapped so that this\n"
" key file can be easily edited with a standard editor.\n"
"Key: (shadowed-private-key\n"
" (rsa\n"
" (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900\n"
" 2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4\n"
" 83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7\n"
" 19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997\n"
" 601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E\n"
" 72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D\n"
" F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0\n"
" 8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A\n"
" E186A02BA2497FDC5D1221#)\n"
" (e #00010001#)\n"
" (shadowed t1-v1\n"
" (#D2760001240102000005000011730000# OPENPGP.1)\n"
" )))\n",
test_key_extraction,
},
};
static char *
nvc_to_string (nvc_t pk)
{
gpg_error_t err;
char *buf;
size_t len;
estream_t sink;
sink = es_fopenmem (0, "rw");
assert (sink);
err = nvc_write (pk, sink);
assert (err == 0);
len = es_ftell (sink);
buf = xmalloc (len+1);
assert (buf);
es_fseek (sink, 0, SEEK_SET);
es_read (sink, buf, len, NULL);
buf[len] = 0;
es_fclose (sink);
return buf;
}
void dummy_free (void *p) { (void) p; }
void *dummy_realloc (void *p, size_t s) { (void) s; return p; }
void
run_tests (void)
{
gpg_error_t err;
nvc_t pk;
int i;
for (i = 0; i < DIM (tests); i++)
{
estream_t source;
char *buf;
size_t len;
len = strlen (tests[i].value);
source = es_mopen (tests[i].value, len, len,
0, dummy_realloc, dummy_free, "r");
assert (source);
if (private_key_mode)
err = nvc_parse_private_key (&pk, NULL, source);
else
err = nvc_parse (&pk, NULL, source);
assert (err == 0);
assert (pk);
if (verbose)
{
err = nvc_write (pk, es_stderr);
assert (err == 0);
}
buf = nvc_to_string (pk);
assert (memcmp (tests[i].value, buf, len) == 0);
es_fclose (source);
xfree (buf);
if (tests[i].test_func)
tests[i].test_func (pk);
nvc_release (pk);
}
}
void
run_modification_tests (void)
{
gpg_error_t err;
nvc_t pk;
nve_t e;
gcry_sexp_t key;
char *buf;
pk = my_nvc_new ();
assert (pk);
nvc_set (pk, "Foo:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Bar\n") == 0);
xfree (buf);
nvc_set (pk, "Foo:", "Baz");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\n") == 0);
xfree (buf);
nvc_set (pk, "Bar:", "Bazzel");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_add (pk, "Foo:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_add (pk, "DontExistYet:", "Bar");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\nDontExistYet: Bar\n")
== 0);
xfree (buf);
nvc_delete (pk, nvc_lookup (pk, "DontExistYet:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nve_next_value (nvc_lookup (pk, "Foo:"), "Foo:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nvc_lookup (pk, "Foo:"));
buf = nvc_to_string (pk);
assert (strcmp (buf, "Bar: Bazzel\n") == 0);
xfree (buf);
nvc_delete (pk, nvc_first (pk));
buf = nvc_to_string (pk);
assert (strcmp (buf, "") == 0);
xfree (buf);
/* Test whether we can delete an entry by name. */
err = nvc_add (pk, "Key:", "(3:foo)");
assert (!err);
e = nvc_lookup (pk, "Key:");
assert (e);
- nvc_delete_named (pk, "Kez:"); /* Delete an inexistant name. */
+ nvc_delete_named (pk, "Kez:"); /* Delete an nonexistent name. */
e = nvc_lookup (pk, "Key:");
assert (e);
nvc_delete_named (pk, "Key:");
e = nvc_lookup (pk, "Key:");
assert (!e);
/* Ditto but now whether it deletes all entries with that name. We
* don't use "Key" because that name is special in private key mode. */
err = nvc_add (pk, "AKey:", "A-value");
assert (!err);
err = nvc_add (pk, "AKey:", "B-value");
assert (!err);
e = nvc_lookup (pk, "AKey:");
assert (e);
nvc_delete_named (pk, "AKey:");
e = nvc_lookup (pk, "AKey:");
assert (!e);
nvc_set (pk, "Foo:", "A really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: A really long value spanning across multiple"
" lines that has to be\n wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "XA really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: XA really long value spanning across multiple"
" lines that has to\n be wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "XXXXA really long value spanning across multiple lines"
" that has to be wrapped at a convenient space.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: XXXXA really long value spanning across multiple"
" lines that has\n to be wrapped at a convenient space.\n")
== 0);
xfree (buf);
nvc_set (pk, "Foo:", "Areallylongvaluespanningacrossmultiplelines"
"thathastobewrappedataconvenientspacethatisnotthere.");
buf = nvc_to_string (pk);
assert (strcmp (buf, "Foo: Areallylongvaluespanningacrossmultiplelinesthat"
"hastobewrappedataco\n nvenientspacethatisnotthere.\n")
== 0);
xfree (buf);
nvc_release (pk);
pk = my_nvc_new ();
assert (pk);
err = gcry_sexp_build (&key, NULL, "(hello world)");
assert (err == 0);
assert (key);
if (private_key_mode)
{
err = nvc_set_private_key (pk, key);
assert (err == 0);
buf = nvc_to_string (pk);
assert (strcmp (buf, "Key: (hello world)\n") == 0);
xfree (buf);
}
else
{
err = nvc_set_private_key (pk, key);
assert (gpg_err_code (err) == GPG_ERR_MISSING_KEY);
}
gcry_sexp_release (key);
nvc_release (pk);
}
void
convert (const char *fname)
{
gpg_error_t err;
estream_t source;
gcry_sexp_t key;
char *buf;
size_t buflen;
struct stat st;
nvc_t pk;
source = es_fopen (fname, "rb");
if (source == NULL)
goto leave;
if (fstat (es_fileno (source), &st))
goto leave;
buflen = st.st_size;
buf = xtrymalloc (buflen+1);
assert (buf);
if (es_fread (buf, buflen, 1, source) != 1)
goto leave;
err = gcry_sexp_sscan (&key, NULL, buf, buflen);
if (err)
{
fprintf (stderr, "malformed s-expression in %s\n", fname);
exit (1);
}
pk = my_nvc_new ();
assert (pk);
err = nvc_set_private_key (pk, key);
assert (err == 0);
err = nvc_write (pk, es_stdout);
assert (err == 0);
return;
leave:
perror (fname);
exit (1);
}
void
parse (const char *fname)
{
gpg_error_t err;
estream_t source;
char *buf;
nvc_t pk_a, pk_b;
nve_t e;
int line;
source = es_fopen (fname, "rb");
if (source == NULL)
{
perror (fname);
exit (1);
}
if (private_key_mode)
err = nvc_parse_private_key (&pk_a, &line, source);
else
err = nvc_parse (&pk_a, &line, source);
if (err)
{
fprintf (stderr, "failed to parse %s line %d: %s\n",
fname, line, gpg_strerror (err));
exit (1);
}
buf = nvc_to_string (pk_a);
xfree (buf);
pk_b = my_nvc_new ();
assert (pk_b);
for (e = nvc_first (pk_a); e; e = nve_next (e))
{
gcry_sexp_t key = NULL;
if (private_key_mode && !strcasecmp (nve_name (e), "Key:"))
{
err = nvc_get_private_key (pk_a, &key);
if (err)
key = NULL;
}
if (key)
{
err = nvc_set_private_key (pk_b, key);
assert (err == 0);
}
else
{
err = nvc_add (pk_b, nve_name (e), nve_value (e));
assert (err == 0);
}
}
buf = nvc_to_string (pk_b);
if (verbose)
fprintf (stdout, "%s", buf);
xfree (buf);
}
void
print_usage (void)
{
fprintf (stderr,
"usage: t-private-keys [--verbose]"
" [--convert <private-key-file>"
" || --parse-key <extended-private-key-file>"
" || --parse <file> ]\n");
exit (2);
}
int
main (int argc, char **argv)
{
enum { TEST, CONVERT, PARSE, PARSEKEY } command = TEST;
if (argc)
{ argc--; argv++; }
if (argc && !strcmp (argv[0], "--verbose"))
{
verbose = 1;
argc--; argv++;
}
if (argc && !strcmp (argv[0], "--convert"))
{
command = CONVERT;
argc--; argv++;
if (argc != 1)
print_usage ();
}
if (argc && !strcmp (argv[0], "--parse-key"))
{
command = PARSEKEY;
argc--; argv++;
if (argc != 1)
print_usage ();
}
if (argc && !strcmp (argv[0], "--parse"))
{
command = PARSE;
argc--; argv++;
if (argc != 1)
print_usage ();
}
switch (command)
{
case TEST:
run_tests ();
run_modification_tests ();
private_key_mode = 1;
run_tests ();
run_modification_tests ();
break;
case CONVERT:
convert (*argv);
break;
case PARSEKEY:
private_key_mode = 1;
parse (*argv);
break;
case PARSE:
parse (*argv);
break;
}
return 0;
}
diff --git a/common/t-openpgp-oid.c b/common/t-openpgp-oid.c
index 56fb6fefe..6736fa6a9 100644
--- a/common/t-openpgp-oid.c
+++ b/common/t-openpgp-oid.c
@@ -1,315 +1,315 @@
/* t-openpgp-oid.c - Module test for openpgp-oid.c
* Copyright (C) 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "util.h"
#define pass() do { ; } while(0)
#define fail(a,e) \
do { fprintf (stderr, "%s:%d: test %d failed (%s)\n", \
__func__, __LINE__, (a), gpg_strerror (e)); \
exit (1); \
} while(0)
#define BADOID "1.3.6.1.4.1.11591.2.12242973"
static int verbose;
static void
test_openpgp_oid_from_str (void)
{
static char *sample_oids[] =
{
"0.0",
"1.0",
"1.2.3",
"1.2.840.10045.3.1.7",
"1.3.132.0.34",
"1.3.132.0.35",
NULL
};
gpg_error_t err;
gcry_mpi_t a;
int idx;
char *string;
unsigned char *p;
unsigned int nbits;
size_t length;
err = openpgp_oid_from_str ("", &a);
if (gpg_err_code (err) != GPG_ERR_INV_VALUE)
fail (0, err);
gcry_mpi_release (a);
err = openpgp_oid_from_str (".", &a);
if (gpg_err_code (err) != GPG_ERR_INV_OID_STRING)
fail (0, err);
gcry_mpi_release (a);
err = openpgp_oid_from_str ("0", &a);
if (gpg_err_code (err) != GPG_ERR_INV_OID_STRING)
fail (0, err);
gcry_mpi_release (a);
for (idx=0; sample_oids[idx]; idx++)
{
err = openpgp_oid_from_str (sample_oids[idx], &a);
if (err)
fail (idx, err);
string = openpgp_oid_to_str (a);
if (!string)
fail (idx, gpg_error_from_syserror ());
if (strcmp (string, sample_oids[idx]))
fail (idx, 0);
xfree (string);
p = gcry_mpi_get_opaque (a, &nbits);
length = (nbits+7)/8;
if (!p || !length || p[0] != length - 1)
fail (idx, 0);
gcry_mpi_release (a);
}
}
static void
test_openpgp_oid_to_str (void)
{
static struct {
const char *string;
unsigned char der[10];
} samples[] = {
{ "1.2.840.10045.3.1.7",
{8, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }},
{ "1.3.132.0.34",
{5, 0x2B, 0x81, 0x04, 0x00, 0x22 }},
{ "1.3.132.0.35",
{ 5, 0x2B, 0x81, 0x04, 0x00, 0x23 }},
{ BADOID,
{ 9, 0x80, 0x02, 0x70, 0x50, 0x25, 0x46, 0xfd, 0x0c, 0xc0 }},
{ BADOID,
{ 1, 0x80 }},
{ NULL }};
gcry_mpi_t a;
int idx;
char *string;
unsigned char *p;
for (idx=0; samples[idx].string; idx++)
{
p = xmalloc (samples[idx].der[0]+1);
memcpy (p, samples[idx].der, samples[idx].der[0]+1);
a = gcry_mpi_set_opaque (NULL, p, (samples[idx].der[0]+1)*8);
if (!a)
fail (idx, gpg_error_from_syserror ());
string = openpgp_oid_to_str (a);
if (!string)
fail (idx, gpg_error_from_syserror ());
if (strcmp (string, samples[idx].string))
fail (idx, 0);
xfree (string);
gcry_mpi_release (a);
/* Again using the buffer variant. */
string = openpgp_oidbuf_to_str (samples[idx].der, samples[idx].der[0]+1);
if (!string)
fail (idx, gpg_error_from_syserror ());
if (strcmp (string, samples[idx].string))
fail (idx, 0);
xfree (string);
}
}
static void
test_openpgp_oid_is_ed25519 (void)
{
static struct
{
int yes;
const char *oidstr;
} samples[] = {
{ 0, "0.0" },
{ 0, "1.3.132.0.35" },
{ 0, "1.3.6.1.4.1.3029.1.5.0" },
{ 0, "1.3.6.1.4.1.3029.1.5.1" }, /* Used during Libgcrypt development. */
{ 0, "1.3.6.1.4.1.3029.1.5.2" },
{ 0, "1.3.6.1.4.1.3029.1.5.1.0" },
{ 0, "1.3.6.1.4.1.3029.1.5" },
{ 0, "1.3.6.1.4.1.11591.15.0" },
{ 1, "1.3.6.1.4.1.11591.15.1" }, /* Your the one we want. */
{ 0, "1.3.6.1.4.1.11591.15.2" },
{ 0, "1.3.6.1.4.1.11591.15.1.0" },
{ 0, "1.3.6.1.4.1.11591.15" },
{ 0, NULL },
};
gpg_error_t err;
gcry_mpi_t a;
int idx;
for (idx=0; samples[idx].oidstr; idx++)
{
err = openpgp_oid_from_str (samples[idx].oidstr, &a);
if (err)
fail (idx, err);
if (openpgp_oid_is_ed25519 (a) != samples[idx].yes)
fail (idx, 0);
gcry_mpi_release (a);
}
}
static void
test_openpgp_enum_curves (void)
{
int iter = 0;
const char *name;
int p256 = 0;
int p384 = 0;
int p521 = 0;
while ((name = openpgp_enum_curves (&iter)))
{
if (verbose)
printf ("curve: %s\n", name);
if (!strcmp (name, "nistp256"))
p256++;
else if (!strcmp (name, "nistp384"))
p384++;
else if (!strcmp (name, "nistp521"))
p521++;
}
if (p256 != 1 || p384 != 1 || p521 != 1)
{
/* We can only check the basic RFC-6637 requirements. */
fputs ("standard ECC curve missing\n", stderr);
exit (1);
}
}
static void
test_get_keyalgo_string (void)
{
static struct
{
int algo;
unsigned int nbits;
const char *curve;
const char *name;
} samples[] =
{
{ GCRY_PK_RSA, 1024, NULL, "rsa1024" },
{ GCRY_PK_RSA, 1536, NULL, "rsa1536" },
{ GCRY_PK_RSA, 768, NULL, "rsa768" },
{ GCRY_PK_DSA, 3072, NULL, "dsa3072" },
{ GCRY_PK_DSA, 1024, NULL, "dsa1024" },
{ GCRY_PK_ELG, 2048, NULL, "elg2048" },
{ GCRY_PK_ELG, 0, NULL, "unknown_20" },
{ 47114711, 1000, NULL, "unknown_47114711" },
/* Note that we don't care about the actual ECC algorithm. */
{ GCRY_PK_EDDSA, 0, "1.3.6.1.4.1.11591.15.1", "ed25519" },
{ GCRY_PK_ECDSA, 0, "1.3.6.1.4.1.11591.15.1", "ed25519" },
{ GCRY_PK_ECDH, 0, "1.3.6.1.4.1.11591.15.1", "ed25519" },
{ GCRY_PK_ECDH, 0, "1.3.6.1.4.1.3029.1.5.1", "cv25519" },
{ GCRY_PK_ECDH, 0, "1.3.36.3.3.2.8.1.1.7", "brainpoolP256r1" },
{ GCRY_PK_ECDH, 0, "1.3.36.3.3.2.8.1.1.11", "brainpoolP384r1" },
{ GCRY_PK_ECDH, 0, "1.3.36.3.3.2.8.1.1.13", "brainpoolP512r1" },
{ GCRY_PK_ECDH, 0, "1.3.132.0.10", "secp256k1" },
{ GCRY_PK_ECDH, 0, "1.2.840.10045.3.1.7", "nistp256" },
{ GCRY_PK_ECDH, 0, "1.3.132.0.34", "nistp384" },
{ GCRY_PK_ECDH, 0, "1.3.132.0.35", "nistp521" },
{ GCRY_PK_ECDH, 0, "1.2.3.4.5.6", "E_1.2.3.4.5.6" },
{ GCRY_PK_ECDH, 0, BADOID, "E_1.3.6.1.4.1.11591.2.12242973" },
/* Some again to test existing lookups. */
{ GCRY_PK_RSA, 768, NULL, "rsa768" },
{ GCRY_PK_DSA, 3072, NULL, "dsa3072" },
{ GCRY_PK_DSA, 1024, NULL, "dsa1024" },
{ GCRY_PK_ECDH, 0, "1.3.6.1.4.1.11591.15.1", "ed25519" },
{ GCRY_PK_ECDH, 0, "1.3.6.1.4.1.3029.1.5.1", "cv25519" },
{ GCRY_PK_ECDH, 0, "1.3.36.3.3.2.8.1.1.7", "brainpoolP256r1" },
{ 47114711, 1000, NULL, "unknown_47114711" }
};
int idx;
const char *name;
int oops = 0;
int pass;
- /* We do several passes becuase that is how the function is
+ /* We do several passes because that is how the function is
* called. */
for (pass=0; pass < 3; pass++)
for (idx=0; idx < DIM (samples); idx++)
{
name = get_keyalgo_string (samples[idx].algo,
samples[idx].nbits,
samples[idx].curve);
if (strcmp (samples[idx].name, name))
{
fprintf (stderr, "%s:test %d.%d: want '%s' got '%s'\n",
__func__, pass, idx, samples[idx].name, name);
oops = 1;
}
}
if (oops)
exit (1);
}
int
main (int argc, char **argv)
{
if (argc)
{ argc--; argv++; }
if (argc && !strcmp (argv[0], "--verbose"))
{
verbose = 1;
argc--; argv++;
}
test_openpgp_oid_from_str ();
test_openpgp_oid_to_str ();
test_openpgp_oid_is_ed25519 ();
test_openpgp_enum_curves ();
test_get_keyalgo_string ();
return 0;
}
diff --git a/common/t-percent.c b/common/t-percent.c
index 774fa80ee..42b54165f 100644
--- a/common/t-percent.c
+++ b/common/t-percent.c
@@ -1,321 +1,321 @@
/* t-percent.c - Module test for percent.c
* Copyright (C) 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "util.h"
#define pass() do { ; } while(0)
#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\
__FILE__,__LINE__, (a)); \
exit (1); \
} while(0)
static void
test_percent_plus_escape (void)
{
static struct {
const char *string;
const char *expect;
} tbl[] = {
{
"",
""
}, {
"a",
"a",
}, {
" ",
"+",
}, {
" ",
"++"
}, {
"+ +",
"%2B+%2B"
}, {
"\" \"",
"%22+%22"
}, {
"%22",
"%2522"
}, {
"%% ",
"%25%25+"
}, {
"\n ABC\t",
"%0A+ABC%09"
}, { NULL, NULL }
};
char *buf, *buf2;
int i;
size_t len;
for (i=0; tbl[i].string; i++)
{
buf = percent_plus_escape (tbl[i].string);
if (!buf)
{
fprintf (stderr, "out of core: %s\n", strerror (errno));
exit (2);
}
if (strcmp (buf, tbl[i].expect))
fail (i);
buf2 = percent_plus_unescape (buf, 0);
if (!buf2)
{
fprintf (stderr, "out of core: %s\n", strerror (errno));
exit (2);
}
if (strcmp (buf2, tbl[i].string))
fail (i);
xfree (buf2);
/* Now test the inplace conversion. */
len = percent_plus_unescape_inplace (buf, 0);
buf[len] = 0;
if (strcmp (buf, tbl[i].string))
fail (i);
xfree (buf);
}
}
static void
test_percent_data_escape (void)
{
static struct {
const char *prefix;
const char *data;
size_t datalen;
const char *expect;
} tbl[] = {
{
NULL,
"", 0,
""
}, {
NULL,
"a", 1,
"a",
}, {
NULL,
"%22", 3,
"%2522"
}, {
NULL,
"%%", 3,
"%25%25%00"
}, {
NULL,
"\n \0BC\t", 6,
"\n %00BC\t"
}, {
"",
"", 0,
""
}, {
"",
"a", 1,
"a",
}, {
"",
"%22", 3,
"%2522"
}, {
"",
"%%", 3,
"%25%25%00"
}, {
"",
"\n \0BC\t", 6,
"\n %00BC\t"
}, {
"a",
"", 0,
"a"
}, {
"a",
"a", 1,
"aa",
}, {
"a",
"%22", 3,
"a%2522"
}, {
"a",
"%%", 3,
"a%25%25%00"
}, {
"a",
"\n \0BC\t", 6,
"a\n %00BC\t"
}, {
" ",
"%%", 3,
" %25%25%00"
}, {
"+",
"%%", 3,
"+%25%25%00"
}, {
"%",
"%%", 3,
"%25%25%25%00"
}, {
"a b",
"%%", 3,
"a b%25%25%00"
}, {
"a%2Bb",
"%%", 3,
"a%252Bb%25%25%00"
}, {
"\n",
"%%", 3,
"%0A%25%25%00"
}, {
NULL,
NULL, 0,
NULL }
};
char *buf;
int i;
size_t len, prefixlen;
for (i=0; tbl[i].data; i++)
{
buf = percent_data_escape (0, tbl[i].prefix, tbl[i].data, tbl[i].datalen);
if (!buf)
{
fprintf (stderr, "out of core: %s\n", strerror (errno));
exit (2);
}
if (strcmp (buf, tbl[i].expect))
{
fail (i);
}
len = percent_plus_unescape_inplace (buf, 0);
prefixlen = tbl[i].prefix? strlen (tbl[i].prefix) : 0;
if (len != tbl[i].datalen + prefixlen)
fail (i);
else if (tbl[i].prefix && memcmp (buf, tbl[i].prefix, prefixlen)
&& !(prefixlen == 1 && *tbl[i].prefix == '+' && *buf == ' '))
{
/* Note extra condition above handles the one test case
* which reverts a plus to a space due to the use of the
- * plus-unescape fucntion also for the prefix part. */
+ * plus-unescape function also for the prefix part. */
fail (i);
}
else if (memcmp (buf+prefixlen, tbl[i].data, tbl[i].datalen))
{
fail (i);
}
xfree (buf);
}
}
static void
test_percent_data_escape_plus (void)
{
static struct {
const char *data;
size_t datalen;
const char *expect;
} tbl[] = {
{
"", 0,
""
}, {
"a", 1,
"a",
}, {
"%22", 3,
"%2522"
}, {
"%%", 3,
"%25%25%00"
}, {
"\n \0BC\t", 6,
"%0A+%00BC%09"
}, {
" ", 1,
"+"
}, {
" ", 2,
"++"
}, {
"+ +", 3,
"%2B+%2B"
}, {
"\" \"", 3, /* Note: This function does not escape quotes. */
"\"+\""
}, {
"%22", 3,
"%2522"
}, {
"%% ", 3,
"%25%25+"
}, {
"\n ABC\t", 6,
"%0A+ABC%09"
}, { NULL, 0, NULL }
};
char *buf;
int i;
size_t len;
for (i=0; tbl[i].data; i++)
{
buf = percent_data_escape (1, NULL, tbl[i].data, tbl[i].datalen);
if (!buf)
{
fprintf (stderr, "out of core: %s\n", strerror (errno));
exit (2);
}
if (strcmp (buf, tbl[i].expect))
{
fail (i);
}
len = percent_plus_unescape_inplace (buf, 0);
if (len != tbl[i].datalen)
fail (i);
else if (memcmp (buf, tbl[i].data, tbl[i].datalen))
fail (i);
xfree (buf);
}
}
int
main (int argc, char **argv)
{
(void)argc;
(void)argv;
/* FIXME: escape_unescape is not tested - only percent_plus_unescape. */
test_percent_plus_escape ();
test_percent_data_escape ();
test_percent_data_escape_plus ();
return 0;
}
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index 88f0aa430..9b80a20ba 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1,2357 +1,2357 @@
/* dirmngr.c - Keyserver and X.509 LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH
* Copyright (C) 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef HAVE_INOTIFY_INIT
# include <sys/inotify.h>
#endif /*HAVE_INOTIFY_INIT*/
#include <npth.h>
#include "dirmngr-err.h"
#if HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
#endif /*HTTP_USE_GNUTLS*/
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "dirmngr.h"
#include <assuan.h>
#include "certcache.h"
#include "crlcache.h"
#include "crlfetch.h"
#include "misc.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "../common/asshelp.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "dns-stuff.h"
#include "http-common.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values {
aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aServer,
aDaemon,
aSupervised,
aListCRLs,
aLoadCRL,
aFetchCRL,
aShutdown,
aFlush,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oDebugLevel,
oGnutlsDebug,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oBatch,
oDisableHTTP,
oDisableLDAP,
oDisableIPv4,
oDisableIPv6,
oIgnoreLDAPDP,
oIgnoreHTTPDP,
oIgnoreOCSPSvcUrl,
oHonorHTTPProxy,
oHTTPProxy,
oLDAPProxy,
oOnlyLDAPProxy,
oLDAPFile,
oLDAPTimeout,
oLDAPAddServers,
oOCSPResponder,
oOCSPSigner,
oOCSPMaxClockSkew,
oOCSPMaxPeriod,
oOCSPCurrentPeriod,
oMaxReplies,
oHkpCaCert,
oFakedSystemTime,
oForce,
oAllowOCSP,
oAllowVersionCheck,
oSocketName,
oLDAPWrapperProgram,
oHTTPWrapperProgram,
oIgnoreCertExtension,
oUseTor,
oNoUseTor,
oKeyServer,
oNameServer,
oDisableCheckOwnSocket,
oStandardResolver,
oRecursiveResolver,
oResolverTimeout,
oConnectTimeout,
oConnectQuickTimeout,
oListenBacklog,
aTest
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_c (aSupervised, "supervised", N_("run in supervised mode")),
#endif
ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")),
ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check",
N_("allow online software version check")),
ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
N_("ignore HTTP CRL distribution points")),
ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
N_("ignore LDAP CRL distribution points")),
ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
N_("ignore certificate contained OCSP service URLs")),
ARGPARSE_s_s (oHTTPProxy, "http-proxy",
N_("|URL|redirect all HTTP requests to URL")),
ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
N_("|HOST|use HOST for LDAP queries")),
ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
N_("do not use fallback hosts with --ldap-proxy")),
ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
N_("|FILE|read LDAP server list from FILE")),
ARGPARSE_s_n (oLDAPAddServers, "add-servers",
N_("add new servers discovered in CRL distribution"
" points to serverlist")),
ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
N_("|N|set LDAP timeout to N seconds")),
ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
N_("|URL|use OCSP responder at URL")),
ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
N_("|FPR|OCSP response signed by FPR")),
ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
ARGPARSE_s_i (oMaxReplies, "max-replies",
N_("|N|do not return more than N items in one query")),
ARGPARSE_s_s (oNameServer, "nameserver", "@"),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")),
ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"),
ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"),
ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"),
ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"),
ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"),
ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"),
ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"),
ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
"of all commands and options)\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_DNS_VALUE , "dns" },
{ DBG_NETWORK_VALUE, "network" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_MAX_REPLIES 10
#define DEFAULT_LDAP_TIMEOUT 15 /* seconds */
#define DEFAULT_CONNECT_TIMEOUT (15*1000) /* 15 seconds */
#define DEFAULT_CONNECT_QUICK_TIMEOUT ( 2*1000) /* 2 seconds */
/* For the cleanup handler we need to keep track of the socket's name. */
static const char *socket_name;
/* If the socket has been redirected, this is the name of the
redirected socket.. */
static const char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument.
* Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Only if this flag has been set will we remove the socket file. */
static int cleanup_socket;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Helper to implement --debug-level. */
static const char *debug_level;
/* Helper to set the NTBTLS or GNUTLS log level. */
static int opt_gnutls_debug = -1;
/* Flag indicating that a shutdown has been requested. */
static volatile int shutdown_pending;
/* Flags to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to control the Tor mode. */
static enum
{ TOR_MODE_AUTO = 0, /* Switch to NO or YES */
TOR_MODE_NEVER, /* Never use Tor. */
TOR_MODE_NO, /* Do not use Tor */
TOR_MODE_YES, /* Use Tor */
TOR_MODE_FORCE /* Force using Tor */
} tor_mode;
/* Counter for the active connections. */
static int active_connections;
/* This flag is set by any network access and used by the housekeeping
* thread to run background network tasks. */
static int network_activity_seen;
/* A list of filenames registered with --hkp-cacert. */
static strlist_t hkp_cacert_filenames;
/* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */
#define TIMERTICK_INTERVAL (60)
#define TIMERTICK_INTERVAL_SHUTDOWN (4)
/* How oft to run the housekeeping. */
#define HOUSEKEEPING_INTERVAL (600)
/* This union is used to avoid compiler warnings in case a pointer is
64 bit and an int 32 bit. We store an integer in a pointer and get
it back later (npth_getspecific et al.). */
union int_and_ptr_u
{
int aint;
assuan_fd_t afd;
void *aptr;
};
/* The key used to store the current file descriptor in the thread
local storage. We use this in conjunction with the
log_set_pid_suffix_cb feature. */
#ifndef HAVE_W32_SYSTEM
static npth_key_t my_tlskey_current_fd;
#endif
/* Prototypes. */
static void cleanup (void);
#if USE_LDAP
static ldap_server_t parse_ldapserver_file (const char* filename);
#endif /*USE_LDAP*/
static fingerprint_list_t parse_ocsp_signer (const char *string);
static void netactivity_action (void);
static void handle_connections (assuan_fd_t listen_fd);
/* NPth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static const char *
my_strusage( int level )
{
const char *p;
switch ( level )
{
case 11: p = "@DIRMNGR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Callback from libksba to hash a provided buffer. Our current
implementation does only allow SHA-1 for hashing. This may be
extended by mapping the name, testing for algorithm availability
and adjust the length checks accordingly. */
static gpg_error_t
my_ksba_hash_buffer (void *arg, const char *oid,
const void *buffer, size_t length, size_t resultsize,
unsigned char *result, size_t *resultlen)
{
(void)arg;
if (oid && strcmp (oid, "1.3.14.3.2.26"))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (resultsize < 20)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
gcry_md_hash_buffer (2, result, buffer, length);
*resultlen = 20;
return 0;
}
/* GNUTLS log function callback. */
#ifdef HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
int n;
n = strlen (text);
while (n && text[n-1] == '\n')
n--;
log_debug ("gnutls:L%d: %.*s\n", level, n, text);
}
#endif /*HTTP_USE_GNUTLS*/
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
log_info (_("valid debug levels are: %s\n"),
"none, basic, advanced, expert, guru");
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
{
opt.verbose = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
}
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
#if HTTP_USE_NTBTLS
if (opt_gnutls_debug >= 0)
{
ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
}
#elif HTTP_USE_GNUTLS
if (opt_gnutls_debug >= 0)
{
gnutls_global_set_log_function (my_gnutls_log);
gnutls_global_set_log_level (opt_gnutls_debug);
}
#endif /*HTTP_USE_GNUTLS*/
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_tor_mode (void)
{
if (dirmngr_use_tor ())
{
/* Enable Tor mode and when called again force a new circuit
* (e.g. on SIGHUP). */
enable_dns_tormode (1);
if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1))
{
log_error ("error enabling Tor mode: %s\n", strerror (errno));
log_info ("(is your Libassuan recent enough?)\n");
}
}
else
disable_dns_tormode ();
}
/* Return true if Tor shall be used. */
int
dirmngr_use_tor (void)
{
if (tor_mode == TOR_MODE_AUTO)
{
/* Figure out whether Tor is running. */
assuan_fd_t sock;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
tor_mode = TOR_MODE_NO;
else
{
tor_mode = TOR_MODE_YES;
assuan_sock_close (sock);
}
}
if (tor_mode == TOR_MODE_FORCE)
return 2; /* Use Tor (using 2 to indicate force mode) */
else if (tor_mode == TOR_MODE_YES)
return 1; /* Use Tor */
else
return 0; /* Do not use Tor. */
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
es_fputs (text, es_stderr);
es_putc ('\n', es_stderr);
dirmngr_exit (2);
}
/* Helper to stop the reaper thread for the ldap wrapper. */
static void
shutdown_reaper (void)
{
#if USE_LDAP
ldap_wrapper_wait_connections ();
#endif
}
/* Handle options which are allowed to be reset after program start.
Return true if the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
if (!pargs)
{ /* Reset mode. */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.ldap_wrapper_program = NULL;
opt.disable_http = 0;
opt.disable_ldap = 0;
opt.honor_http_proxy = 0;
opt.http_proxy = NULL;
opt.ldap_proxy = NULL;
opt.only_ldap_proxy = 0;
opt.ignore_http_dp = 0;
opt.ignore_ldap_dp = 0;
opt.ignore_ocsp_service_url = 0;
opt.allow_ocsp = 0;
opt.allow_version_check = 0;
opt.ocsp_responder = NULL;
opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
opt.ocsp_max_period = 90 * 86400; /* 90 days. */
opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
opt.max_replies = DEFAULT_MAX_REPLIES;
while (opt.ocsp_signer)
{
fingerprint_list_t tmp = opt.ocsp_signer->next;
xfree (opt.ocsp_signer);
opt.ocsp_signer = tmp;
}
FREE_STRLIST (opt.ignored_cert_extensions);
http_register_tls_ca (NULL);
FREE_STRLIST (hkp_cacert_filenames);
FREE_STRLIST (opt.keyserver);
/* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_AUTO;
disable_check_own_socket = 0;
enable_standard_resolver (0);
set_dns_timeout (0);
opt.connect_timeout = 0;
opt.connect_quick_timeout = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
case oLogFile:
if (!reread)
return 0; /* Not handled. */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oLDAPWrapperProgram:
opt.ldap_wrapper_program = pargs->r.ret_str;
break;
case oHTTPWrapperProgram:
opt.http_wrapper_program = pargs->r.ret_str;
break;
case oDisableHTTP: opt.disable_http = 1; break;
case oDisableLDAP: opt.disable_ldap = 1; break;
case oDisableIPv4: opt.disable_ipv4 = 1; break;
case oDisableIPv6: opt.disable_ipv6 = 1; break;
case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
case oAllowOCSP: opt.allow_ocsp = 1; break;
case oAllowVersionCheck: opt.allow_version_check = 1; break;
case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
case oOCSPSigner:
opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str);
break;
case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
case oHkpCaCert:
{
/* We need to register the filenames with gnutls (http.c) and
* also for our own cert cache. */
char *tmpname;
/* Do tilde expansion and make path absolute. */
tmpname = make_absfilename (pargs->r.ret_str, NULL);
http_register_tls_ca (tmpname);
add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str);
xfree (tmpname);
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
break;
case oUseTor:
tor_mode = TOR_MODE_FORCE;
break;
case oNoUseTor:
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_NEVER;
break;
case oStandardResolver: enable_standard_resolver (1); break;
case oRecursiveResolver: enable_recursive_resolver (1); break;
case oKeyServer:
if (*pargs->r.ret_str)
add_to_strlist (&opt.keyserver, pargs->r.ret_str);
break;
case oNameServer:
set_dns_nameserver (pargs->r.ret_str);
break;
case oResolverTimeout:
set_dns_timeout (pargs->r.ret_int);
break;
case oConnectTimeout:
opt.connect_timeout = pargs->r.ret_ulong * 1000;
break;
case oConnectQuickTimeout:
opt.connect_quick_timeout = pargs->r.ret_ulong * 1000;
break;
default:
return 0; /* Not handled. */
}
set_dns_verbose (opt.verbose, !!DBG_DNS);
http_set_verbose (opt.verbose, !!DBG_NETWORK);
set_dns_disable_ipv4 (opt.disable_ipv4);
set_dns_disable_ipv6 (opt.disable_ipv6);
return 1; /* Handled. */
}
/* This function is called after option parsing to adjust some values
* and call option setup functions. */
static void
post_option_parsing (void)
{
/* It would be too surpirsing if the quick timeout is larger than
* the standard value. */
if (opt.connect_quick_timeout > opt.connect_timeout)
opt.connect_quick_timeout = opt.connect_timeout;
set_debug ();
}
#ifndef HAVE_W32_SYSTEM
static int
pid_suffix_callback (unsigned long *r_suffix)
{
union int_and_ptr_u value;
memset (&value, 0, sizeof value);
value.aptr = npth_getspecific (my_tlskey_current_fd);
*r_suffix = value.aint;
return (*r_suffix != -1); /* Use decimal representation. */
}
#endif /*!HAVE_W32_SYSTEM*/
#if HTTP_USE_NTBTLS
static void
my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv)
{
(void)opaque;
if (level == -1)
log_logv_prefix (GPGRT_LOGLVL_INFO, "ntbtls: ", fmt, argv);
else
{
char prefix[10+20];
snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level);
log_logv_prefix (GPGRT_LOGLVL_DEBUG, prefix, fmt, argv);
}
}
#endif
static void
thread_init (void)
{
npth_init ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now with NPth running we can set the logging callback. Our
windows implementation does not yet feature the NPth TLS
functions. */
#ifndef HAVE_W32_SYSTEM
if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
log_set_pid_suffix_cb (pid_suffix_callback);
#endif /*!HAVE_W32_SYSTEM*/
}
int
main (int argc, char **argv)
{
enum cmd_and_opt_values cmd = 0;
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
#if USE_LDAP
char *ldapfile = NULL;
#endif /*USE_LDAP*/
int debug_wait = 0;
int rc;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
set_strusage (my_strusage);
log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the libraries. */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
/* Init TLS library. */
#if HTTP_USE_NTBTLS
if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
/* Init Assuan. */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor);
setup_libgcrypt_logging ();
#if HTTP_USE_NTBTLS
ntbtls_set_log_handler (my_ntbtls_log_handler, NULL);
#endif
/* Setup defaults. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Reset rereadable options to default values. */
parse_rereadable_options (NULL, 0);
/* Default TCP timeouts. */
opt.connect_timeout = DEFAULT_CONNECT_TIMEOUT;
opt.connect_quick_timeout = DEFAULT_CONNECT_QUICK_TIMEOUT;
/* LDAP defaults. */
opt.add_new_ldapservers = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
/* Other defaults. */
/* Check whether we have a config file given on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
{
gnupg_set_homedir (pargs.r.ret_str);
}
}
socket_name = dirmngr_socket_name ();
if (default_config)
configname = make_filename (gnupg_homedir (), DIRMNGR_NAME".conf", NULL );
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aServer:
case aDaemon:
case aSupervised:
case aShutdown:
case aFlush:
case aListCRLs:
case aLoadCRL:
case aFetchCRL:
case aGPGConfList:
case aGPGConfTest:
cmd = pargs.r_opt;
break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* Config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: /* Ignore this option here. */; break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oLDAPFile:
# if USE_LDAP
ldapfile = pargs.r.ret_str;
# endif /*USE_LDAP*/
break;
case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
case oLDAPTimeout:
opt.ldaptimeout = pargs.r.ret_int;
break;
case oFakedSystemTime:
gnupg_set_time ((time_t)pargs.r.ret_ulong, 0);
break;
case oForce: opt.force = 1; break;
case oSocketName: socket_name = pargs.r.ret_str; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
opt.config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (!opt.homedir_cache)
opt.homedir_cache = xstrdup (gnupg_homedir ());
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (!access ("/etc/"DIRMNGR_NAME, F_OK)
&& !strncmp (gnupg_homedir (), "/etc/", 5))
log_info
("NOTE: DirMngr is now a proper part of %s. The configuration and"
" other directory names changed. Please check that no other version"
" of dirmngr is still installed. To disable this warning, remove the"
" directory '/etc/dirmngr'.\n", GNUPG_NAME);
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Note that we do not run set_tor_mode in --gpgconf-list mode
* because it will attempt to connect to the tor client and that can
* be time consuming. */
post_option_parsing ();
if (cmd != aGPGConfTest && cmd != aGPGConfList)
set_tor_mode ();
/* Get LDAP server list from file. */
#if USE_LDAP
if (!ldapfile)
{
ldapfile = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
opt.ldapservers = parse_ldapserver_file (ldapfile);
xfree (ldapfile);
}
else
opt.ldapservers = parse_ldapserver_file (ldapfile);
#endif /*USE_LDAP*/
#ifndef HAVE_W32_SYSTEM
/* We need to ignore the PIPE signal because the we might log to a
socket and that code handles EPIPE properly. The ldap wrapper
also requires us to ignore this silly signal. Assuan would set
this signal to ignore anyway.*/
signal (SIGPIPE, SIG_IGN);
#endif
/* Ready. Now to our duties. */
if (!cmd)
cmd = aServer;
rc = 0;
if (cmd == aServer)
{
/* Note that this server mode is mainly useful for debugging. */
if (argc)
wrong_args ("--server");
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
start_command_handler (ASSUAN_INVALID_FD, 0);
shutdown_reaper ();
}
#ifndef HAVE_W32_SYSTEM
else if (cmd == aSupervised)
{
/* In supervised mode, we expect file descriptor 3 to be an
already opened, listening socket.
We will also not detach from the controlling process or close
stderr; the supervisor should handle all of that. */
struct stat statbuf;
if (fstat (3, &statbuf) == -1 && errno == EBADF)
{
log_error ("file descriptor 3 must be validin --supervised mode\n");
dirmngr_exit (1);
}
socket_name = gnupg_get_socket_name (3);
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
else
log_set_prefix (NULL, 0);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (3);
shutdown_reaper ();
}
#endif /*HAVE_W32_SYSTEM*/
else if (cmd == aDaemon)
{
assuan_fd_t fd;
pid_t pid;
int len;
struct sockaddr_un serv_addr;
if (argc)
wrong_args ("--daemon");
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
#ifndef HAVE_W32_SYSTEM
if (strchr (socket_name, ':'))
{
log_error (_("colons are not allowed in the socket name\n"));
dirmngr_exit (1);
}
#endif
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
cleanup ();
dirmngr_exit (1);
}
{
int redirected;
if (assuan_sock_set_sockaddr_un (socket_name,
(struct sockaddr*)&serv_addr,
&redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), socket_name);
else
log_error ("error preparing socket '%s': %s\n",
socket_name,
gpg_strerror (gpg_error_from_syserror ()));
dirmngr_exit (1);
}
if (redirected)
{
redir_socket_name = xstrdup (serv_addr.sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n",
socket_name, redir_socket_name);
}
}
len = SUN_LEN (&serv_addr);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Fixme: We should test whether a dirmngr is already running. */
gnupg_remove (redir_socket_name? redir_socket_name : socket_name);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
}
if (rc != -1
&& (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
serv_addr.sun_path,
gpg_strerror (gpg_error_from_errno (errno)));
assuan_sock_close (fd);
dirmngr_exit (1);
}
cleanup_socket = 1;
if (gnupg_chmod (serv_addr.sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
serv_addr.sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
assuan_sock_close (fd);
dirmngr_exit (1);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), serv_addr.sun_path);
es_fflush (NULL);
/* Note: We keep the dirmngr_info output only for the sake of
existing scripts which might use this to detect a successful
start of the dirmngr. */
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
pid = getpid ();
es_printf ("set %s=%s;%lu;1\n",
DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
#else
pid = fork();
if (pid == (pid_t)-1)
{
log_fatal (_("error forking process: %s\n"), strerror (errno));
dirmngr_exit (1);
}
if (pid)
{ /* We are the parent */
char *infostr;
/* Don't let cleanup() remove the socket - the child is
responsible for doing that. */
cleanup_socket = 0;
close (fd);
/* Create the info string: <name>:<pid>:<protocol_version> */
if (asprintf (&infostr, "%s=%s:%lu:1",
DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0)
{
log_error (_("out of core\n"));
kill (pid, SIGTERM);
dirmngr_exit (1);
}
/* Print the environment string, so that the caller can use
shell's eval to set it. But see above. */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
}
free (infostr);
exit (0);
/*NEVER REACHED*/
} /* end parent */
/*
This is the child
*/
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
dirmngr_exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
dirmngr_exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
}
#endif
if (!nodetach )
{
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
dirmngr_exit (1);
}
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (fd);
shutdown_reaper ();
}
else if (cmd == aListCRLs)
{
/* Just list the CRL cache and exit. */
if (argc)
wrong_args ("--list-crls");
crl_cache_init ();
crl_cache_list (es_stdout);
}
else if (cmd == aLoadCRL)
{
struct server_control_s ctrlbuf;
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
if (!argc)
rc = crl_cache_load (&ctrlbuf, NULL);
else
{
for (; !rc && argc; argc--, argv++)
rc = crl_cache_load (&ctrlbuf, *argv);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFetchCRL)
{
ksba_reader_t reader;
struct server_control_s ctrlbuf;
if (argc != 1)
wrong_args ("--fetch-crl URL");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
rc = crl_fetch (&ctrlbuf, argv[0], &reader);
if (rc)
log_error (_("fetching CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
else
{
rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
if (rc)
log_error (_("processing CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
crl_close_reader (reader);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFlush)
{
/* Delete cache and exit. */
if (argc)
wrong_args ("--flush");
rc = crl_cache_flush();
}
else if (cmd == aGPGConfTest)
dirmngr_exit (0);
else if (cmd == aGPGConfList)
{
unsigned long flags = 0;
char *filename;
char *filename_esc;
/* First the configuration file. This is not an option, but it
is vital information for GPG Conf. */
if (!opt.config_filename)
opt.config_filename = make_filename (gnupg_homedir (),
"dirmngr.conf", NULL );
filename = percent_escape (opt.config_filename, NULL);
es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n",
GC_OPT_FLAG_DEFAULT, filename);
xfree (filename);
es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* --csh and --sh are mutually exclusive, something we can not
express in GPG Conf. --options is only usable from the
command line, really. --debug-all interacts with --debug,
and having both of them is thus problematic. --no-detach is
also only usable on the command line. --batch is unused. */
filename = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("ldaptimeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
es_printf ("max-replies:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("allow-version-check:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE);
es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
/* Note: The next one is to fix a typo in gpgconf - should be
removed eventually. */
es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE);
filename_esc = percent_escape (get_default_keyserver (0), NULL);
es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
es_printf ("nameserver:%lu:\n", flags | GC_OPT_FLAG_NONE);
es_printf ("resolver-timeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, 0);
}
cleanup ();
return !!rc;
}
static void
cleanup (void)
{
crl_cache_deinit ();
cert_cache_deinit (1);
reload_dns_stuff (1);
#if USE_LDAP
ldapserver_list_free (opt.ldapservers);
#endif /*USE_LDAP*/
opt.ldapservers = NULL;
if (cleanup_socket)
{
cleanup_socket = 0;
if (redir_socket_name)
gnupg_remove (redir_socket_name);
else if (socket_name && *socket_name)
gnupg_remove (socket_name);
}
}
void
dirmngr_exit (int rc)
{
cleanup ();
exit (rc);
}
void
dirmngr_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
if (opt.http_proxy)
ctrl->http_proxy = xstrdup (opt.http_proxy);
ctrl->http_no_crl = 1;
ctrl->timeout = opt.connect_timeout;
}
void
dirmngr_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
ctrl->magic = 0xdeadbeef;
xfree (ctrl->http_proxy);
ctrl->http_proxy = NULL;
}
/* Create a list of LDAP servers from the file FILENAME. Returns the
list or NULL in case of errors.
- The format fo such a file is line oriented where empty lines and
+ The format of such a file is line oriented where empty lines and
lines starting with a hash mark are ignored. All other lines are
assumed to be colon seprated with these fields:
1. field: Hostname
2. field: Portnumber
3. field: Username
4. field: Password
5. field: Base DN
*/
#if USE_LDAP
static ldap_server_t
parse_ldapserver_file (const char* filename)
{
char buffer[1024];
char *p;
ldap_server_t server, serverstart, *serverend;
int c;
unsigned int lineno = 0;
estream_t fp;
fp = es_fopen (filename, "r");
if (!fp)
{
log_info ("failed to open '%s': %s\n", filename, strerror (errno));
return NULL;
}
serverstart = NULL;
serverend = &serverstart;
while (es_fgets (buffer, sizeof buffer, fp))
{
lineno++;
if (!*buffer || buffer[strlen(buffer)-1] != '\n')
{
if (*buffer && es_feof (fp))
; /* Last line not terminated - continue. */
else
{
log_error (_("%s:%u: line too long - skipped\n"),
filename, lineno);
while ( (c=es_fgetc (fp)) != EOF && c != '\n')
; /* Skip until end of line. */
continue;
}
}
/* Skip empty and comment lines.*/
for (p=buffer; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
/* Parse the colon separated fields. */
server = ldapserver_parse_one (buffer, filename, lineno);
if (server)
{
*serverend = server;
serverend = &server->next;
}
}
if (es_ferror (fp))
log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
es_fclose (fp);
return serverstart;
}
#endif /*USE_LDAP*/
static fingerprint_list_t
parse_ocsp_signer (const char *string)
{
gpg_error_t err;
char *fname;
estream_t fp;
char line[256];
char *p;
fingerprint_list_t list, *list_tail, item;
unsigned int lnr = 0;
int c, i, j;
int errflag = 0;
/* Check whether this is not a filename and treat it as a direct
fingerprint specification. */
if (!strpbrk (string, "/.~\\"))
{
item = xcalloc (1, sizeof *item);
for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
if ( string[i] != ':' )
item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (string+i) || !string[i]))
{
log_error (_("%s:%u: invalid fingerprint detected\n"),
"--ocsp-signer", 0);
xfree (item);
return NULL;
}
return item;
}
/* Well, it is a filename. */
if (*string == '/' || (*string == '~' && string[1] == '/'))
fname = make_filename (string, NULL);
else
{
if (string[0] == '.' && string[1] == '/' )
string += 2;
fname = make_filename (gnupg_homedir (), string, NULL);
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
list = NULL;
list_tail = &list;
for (;;)
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
if (!es_feof (fp))
{
err = gpg_error_from_syserror ();
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
}
es_fclose (fp);
if (errflag)
{
while (list)
{
fingerprint_list_t tmp = list->next;
xfree (list);
list = tmp;
}
}
xfree (fname);
return list; /* Ready. */
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
/* */: GPG_ERR_INCOMPLETE_LINE);
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
continue;
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
item = xcalloc (1, sizeof *item);
*list_tail = item;
list_tail = &item->next;
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
errflag = 1;
}
i++;
while (spacep (p+i))
i++;
if (p[i] && p[i] != '\n')
log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
}
/*NOTREACHED*/
}
/*
Stuff used in daemon mode.
*/
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the NPTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
- allocated or points into the process' original arguments. Unless
+ allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!opt.config_filename)
return; /* No config file. */
fp = fopen (opt.config_filename, "r");
if (!fp)
{
log_error (_("option file '%s': %s\n"),
opt.config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
post_option_parsing ();
}
/* A global function which allows us to trigger the reload stuff from
other places. */
void
dirmngr_sighup_action (void)
{
log_info (_("SIGHUP received - "
"re-reading configuration and flushing caches\n"));
reread_configuration ();
set_tor_mode ();
cert_cache_deinit (0);
crl_cache_deinit ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
reload_dns_stuff (0);
ks_hkp_reload ();
}
/* This function is called if some network activity was done. At this
* point we know the we have a network and we can decide whether to
* run scheduled background tasks soon. The function should return
* quickly and only trigger actions for another thread. */
static void
netactivity_action (void)
{
network_activity_seen = 1;
}
/* The signal handler. */
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
dirmngr_sighup_action ();
break;
case SIGUSR1:
cert_cache_print_stats ();
domaininfo_print_stats ();
break;
case SIGUSR2:
log_info (_("SIGUSR2 received - no action defined\n"));
break;
case SIGTERM:
if (!shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
else
log_info (_("SIGTERM received - still %d active connections\n"),
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
dirmngr_exit (0);
}
break;
case SIGINT:
log_info (_("SIGINT received - immediate shutdown\n"));
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
dirmngr_exit (0);
break;
default:
log_info (_("signal %d received - no action defined\n"), signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Thread to do the housekeeping. */
static void *
housekeeping_thread (void *arg)
{
static int sentinel;
time_t curtime;
struct server_control_s ctrlbuf;
(void)arg;
curtime = gnupg_get_time ();
if (sentinel)
{
log_info ("housekeeping is already going on\n");
return NULL;
}
sentinel++;
if (opt.verbose > 1)
log_info ("starting housekeeping\n");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
dns_stuff_housekeeping ();
ks_hkp_housekeeping (curtime);
if (network_activity_seen)
{
network_activity_seen = 0;
if (opt.allow_version_check)
dirmngr_load_swdb (&ctrlbuf, 0);
workqueue_run_global_tasks (&ctrlbuf, 1);
}
else
workqueue_run_global_tasks (&ctrlbuf, 0);
dirmngr_deinit_default_ctrl (&ctrlbuf);
if (opt.verbose > 1)
log_info ("ready with housekeeping\n");
sentinel--;
return NULL;
}
/* We try to enable correct overflow handling for signed int (commonly
* used for time_t). With gcc 4.2 -fno-strict-overflow was introduced
* and used here as a pragma. Later gcc versions (gcc 6?) removed
* this as a pragma and -fwrapv was then suggested as a replacement
* for -fno-strict-overflow. */
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC push_options
# pragma GCC optimize ("wrapv")
#endif
static int
time_for_housekeeping_p (time_t curtime)
{
static time_t last_housekeeping;
if (!last_housekeeping)
last_housekeeping = curtime;
if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
|| last_housekeeping > curtime /*(be prepared for y2038)*/)
{
last_housekeeping = curtime;
return 1;
}
return 0;
}
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC pop_options
#endif
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
struct stat statbuf;
if (time_for_housekeeping_p (gnupg_get_time ()))
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
log_error ("error preparing housekeeping thread: %s\n", strerror (err));
else
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
if (err)
log_error ("error spawning housekeeping thread: %s\n",
strerror (err));
npth_attr_destroy (&tattr);
}
}
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* Check the nonce on a new connection. This is a NOP unless we are
using our Unix domain socket emulation under Windows. */
static int
check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT (fd), strerror (errno));
assuan_sock_close (fd);
return -1;
}
else
return 0;
}
/* Helper to call a connection's main function. */
static void *
start_connection_thread (void *arg)
{
static unsigned int last_session_id;
unsigned int session_id;
union int_and_ptr_u argval;
gnupg_fd_t fd;
memset (&argval, 0, sizeof argval);
argval.aptr = arg;
fd = argval.afd;
if (check_nonce (fd, &socket_nonce))
{
log_error ("handler nonce check FAILED\n");
return NULL;
}
#ifndef HAVE_W32_SYSTEM
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
active_connections++;
if (opt.verbose)
log_info (_("handler for fd %d started\n"), FD2INT (fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
start_command_handler (fd, session_id);
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"), FD2INT (fd));
active_connections--;
workqueue_run_post_session_tasks (session_id);
#ifndef HAVE_W32_SYSTEM
argval.afd = ASSUAN_INVALID_FD;
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
return NULL;
}
#ifdef HAVE_INOTIFY_INIT
/* Read an inotify event and return true if it matches NAME. */
static int
my_inotify_is_name (int fd, const char *name)
{
union {
struct inotify_event ev;
char _buf[sizeof (struct inotify_event) + 100 + 1];
} buf;
int n;
const char *s;
s = strrchr (name, '/');
if (s && s[1])
name = s + 1;
n = npth_read (fd, &buf, sizeof buf);
if (n < sizeof (struct inotify_event))
return 0;
if (buf.ev.len < strlen (name)+1)
return 0;
if (strcmp (buf.ev.name, name))
return 0; /* Not the desired file. */
return 1; /* Found. */
}
#endif /*HAVE_INOTIFY_INIT*/
/* Main loop in daemon mode. Note that LISTEN_FD will be owned by
* this function. */
static void
handle_connections (assuan_fd_t listen_fd)
{
npth_attr_t tattr;
#ifndef HAVE_W32_SYSTEM
int signo;
#endif
struct sockaddr_un paddr;
socklen_t plen = sizeof( paddr );
int nfd, ret;
fd_set fdset, read_fdset;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
int my_inotify_fd = -1;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM /* FIXME */
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
#ifdef HAVE_INOTIFY_INIT
if (disable_check_own_socket)
my_inotify_fd = -1;
else if ((my_inotify_fd = inotify_init ()) == -1)
log_info ("error enabling fast daemon termination: %s\n",
strerror (errno));
else
{
/* We need to watch the directory for the file because there
* won't be an IN_DELETE_SELF for a socket file. */
char *slash = strrchr (socket_name, '/');
log_assert (slash && slash[1]);
*slash = 0;
if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1)
{
close (my_inotify_fd);
my_inotify_fd = -1;
}
*slash = '/';
}
#endif /*HAVE_INOTIFY_INIT*/
/* Setup the fdset. It has only one member. This is because we use
pth_select instead of pth_accept to properly sync timeouts with
to full second. */
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
if (my_inotify_fd > nfd)
nfd = my_inotify_fd;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
/* Main loop. */
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
/* assuan_sock_close (listen_fd); */
/* listen_fd = -1; */
FD_ZERO (&fdset);
nfd = -1;
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
nfd = my_inotify_fd;
}
}
/* Take a copy of the fdset. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. When a shutdown is pending we use a shorter
* interval to handle the shutdown more quickly. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += (shutdown_pending
? TIMERTICK_INTERVAL_SHUTDOWN
: TIMERTICK_INTERVAL);
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
if (shutdown_pending)
{
/* Do not anymore accept connections. */
continue;
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset)
&& my_inotify_is_name (my_inotify_fd, socket_name))
{
shutdown_pending = 1;
log_info ("socket file has been removed - shutting down\n");
}
#endif /*HAVE_INOTIFY_INIT*/
if (FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
gnupg_fd_t fd;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listen_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else
{
char threadname[50];
union int_and_ptr_u argval;
npth_t thread;
memset (&argval, 0, sizeof argval);
argval.afd = fd;
snprintf (threadname, sizeof threadname,
"conn fd=%d", FD2INT(fd));
ret = npth_create (&thread, &tattr,
start_connection_thread, argval.aptr);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret) );
assuan_sock_close (fd);
}
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1)
close (my_inotify_fd);
#endif /*HAVE_INOTIFY_INIT*/
npth_attr_destroy (&tattr);
if (listen_fd != GNUPG_INVALID_FD)
assuan_sock_close (listen_fd);
cleanup ();
log_info ("%s %s stopped\n", strusage(11), strusage(13));
}
const char*
dirmngr_get_current_socket_name (void)
{
if (socket_name)
return socket_name;
else
return dirmngr_socket_name ();
}
diff --git a/dirmngr/dns.c b/dirmngr/dns.c
index 2cc1c7049..9d5570821 100644
--- a/dirmngr/dns.c
+++ b/dirmngr/dns.c
@@ -1,11562 +1,11562 @@
/* ==========================================================================
* dns.c - Recursive, Reentrant DNS Resolver.
* --------------------------------------------------------------------------
* Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#if HAVE_CONFIG_H
#include "config.h"
#elif !defined _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <limits.h> /* INT_MAX */
#include <stdarg.h> /* va_list va_start va_end */
#include <stddef.h> /* offsetof() */
#ifdef _WIN32
/* JW: This breaks our mingw build: #define uint32_t unsigned int */
#else
#include <stdint.h> /* uint32_t */
#endif
#include <stdlib.h> /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */
#include <stdio.h> /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */
#include <string.h> /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */
#include <strings.h> /* strcasecmp(3) strncasecmp(3) */
#include <ctype.h> /* isspace(3) isdigit(3) */
#include <time.h> /* time_t time(2) difftime(3) */
#include <signal.h> /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */
#include <errno.h> /* errno EINVAL ENOENT */
#undef NDEBUG
#include <assert.h> /* assert(3) */
#if _WIN32
#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
typedef SOCKET socket_fd_t;
#define STDCALL __stdcall
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h> /* gettimeofday(2) */
#endif
#else
typedef int socket_fd_t;
#define STDCALL
#include <sys/time.h> /* gettimeofday(2) */
#include <sys/types.h> /* FD_SETSIZE socklen_t */
#include <sys/select.h> /* FD_ZERO FD_SET fd_set select(2) */
#include <sys/socket.h> /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */
#if defined(AF_UNIX)
#include <sys/un.h> /* struct sockaddr_un */
#endif
#include <fcntl.h> /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */
#include <unistd.h> /* _POSIX_THREADS gethostname(3) close(2) */
#include <poll.h> /* POLLIN POLLOUT */
#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */
#include <arpa/inet.h> /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */
#include <netdb.h> /* struct addrinfo */
#endif
#include "gpgrt.h" /* For GGPRT_GCC_VERSION */
#include "dns.h"
/*
* C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p))
#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p)))
#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p))
#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p)))
#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p)
#if defined __has_builtin
#define dns_has_builtin(x) __has_builtin(x)
#else
#define dns_has_builtin(x) 0
#endif
#if defined __has_extension
#define dns_has_extension(x) __has_extension(x)
#else
#define dns_has_extension(x) 0
#endif
#ifndef HAVE___ASSUME
#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0)
#endif
#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__)
#endif
#ifndef HAVE___BUILTIN_UNREACHABLE
#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable))
#endif
#ifndef HAVE_PRAGMA_MESSAGE
#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER)
#endif
/*
* C O M P I L E R A N N O T A T I O N S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if __GNUC__
#define DNS_NOTUSED __attribute__((unused))
#define DNS_NORETURN __attribute__((noreturn))
#else
#define DNS_NOTUSED
#define DNS_NORETURN
#endif
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
/*
* S T A N D A R D M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b))
#else
#define dns_same_type(a, b, def) (def)
#endif
#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0))
/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */
#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; })))
#if HAVE___ASSUME
#define dns_assume(cond) __assume(cond)
#elif HAVE___BUILTIN_UNREACHABLE
#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#define dns_assume(cond) do { (void)(cond); } while (0)
#endif
#ifndef lengthof
#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0]))
#endif
#ifndef endof
#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))])
#endif
/*
* M I S C E L L A N E O U S C O M P A T
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if _WIN32 || _WIN64
#define PRIuZ "Iu"
#else
#define PRIuZ "zu"
#endif
#ifndef DNS_THREAD_SAFE
#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0
#define DNS_THREAD_SAFE 1
#else
#define DNS_THREAD_SAFE 0
#endif
#endif
#ifndef HAVE__STATIC_ASSERT
#define HAVE__STATIC_ASSERT \
(dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \
__C11FEATURES__ || __STDC_VERSION__ >= 201112L)
#endif
#ifndef HAVE_STATIC_ASSERT
#if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0)
#define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */
#elif defined(static_assert)
#define HAVE_STATIC_ASSERT 1
#else
#define HAVE_STATIC_ASSERT 0
#endif
#endif
#if HAVE_STATIC_ASSERT
#define dns_static_assert(cond, msg) static_assert(cond, msg)
#elif HAVE__STATIC_ASSERT
#define dns_static_assert(cond, msg) _Static_assert(cond, msg)
#else
#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])]
#endif
/*
* D E B U G M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int *dns_debug_p(void) {
static int debug;
return &debug;
} /* dns_debug_p() */
#if DNS_DEBUG
#undef DNS_DEBUG
#define DNS_DEBUG dns_debug
#define DNS_SAY_(fmt, ...) \
do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0)
#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n")
#define DNS_HAI DNS_SAY("HAI")
#define DNS_SHOW_(P, fmt, ...) do { \
if (DNS_DEBUG > 1) { \
fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \
fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \
dns_p_dump((P), stderr); \
fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \
} \
} while (0)
#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "")
#else /* !DNS_DEBUG */
#undef DNS_DEBUG
#define DNS_DEBUG 0
#define DNS_SAY(...)
#define DNS_HAI
#define DNS_SHOW(...)
#endif /* DNS_DEBUG */
#define DNS_CARP(...) DNS_SAY(__VA_ARGS__)
/*
* V E R S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const char *dns_vendor(void) {
return DNS_VENDOR;
} /* dns_vendor() */
int dns_v_rel(void) {
return DNS_V_REL;
} /* dns_v_rel() */
int dns_v_abi(void) {
return DNS_V_ABI;
} /* dns_v_abi() */
int dns_v_api(void) {
return DNS_V_API;
} /* dns_v_api() */
/*
* E R R O R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EPROTO
# define EPROTO EPROTONOSUPPORT
#endif
#if _WIN32
#define DNS_EINTR WSAEINTR
#define DNS_EINPROGRESS WSAEINPROGRESS
#define DNS_EISCONN WSAEISCONN
#define DNS_EWOULDBLOCK WSAEWOULDBLOCK
#define DNS_EALREADY WSAEALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT WSAETIMEDOUT
#define dns_syerr() ((int)GetLastError())
#define dns_soerr() ((int)WSAGetLastError())
#else
#define DNS_EINTR EINTR
#define DNS_EINPROGRESS EINPROGRESS
#define DNS_EISCONN EISCONN
#define DNS_EWOULDBLOCK EWOULDBLOCK
#define DNS_EALREADY EALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT ETIMEDOUT
#define dns_syerr() errno
#define dns_soerr() errno
#endif
const char *dns_strerror(int error) {
switch (error) {
case DNS_ENOBUFS:
return "DNS packet buffer too small";
case DNS_EILLEGAL:
return "Illegal DNS RR name or data";
case DNS_EORDER:
return "Attempt to push RR out of section order";
case DNS_ESECTION:
return "Invalid section specified";
case DNS_EUNKNOWN:
return "Unknown DNS error";
case DNS_EADDRESS:
return "Invalid textual address form";
case DNS_ENOQUERY:
return "Bad execution state (missing query packet)";
case DNS_ENOANSWER:
return "Bad execution state (missing answer packet)";
case DNS_EFETCHED:
return "Answer already fetched";
case DNS_ESERVICE:
return "The service passed was not recognized for the specified socket type";
case DNS_ENONAME:
return "The name does not resolve for the supplied parameters";
case DNS_EFAIL:
return "A non-recoverable error occurred when attempting to resolve the name";
case DNS_ECONNFIN:
return "Connection closed";
case DNS_EVERIFY:
return "Reply failed verification";
default:
return strerror(error);
} /* switch() */
} /* dns_strerror() */
/*
* A T O M I C R O U T I N E S
*
* Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we
* can use the preprocessor to detect API and, more importantly, ISA
* support. We want to avoid linking headaches where the API depends on an
* external library if the ISA (e.g. i386) doesn't support lockless
* operation.
*
* TODO: Support C11's atomic API. Although that may require some finesse
* with how we define some public types, such as dns_atomic_t and struct
* dns_resolv_conf.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HAVE___ATOMIC_FETCH_ADD
#ifdef __ATOMIC_RELAXED
#define HAVE___ATOMIC_FETCH_ADD 1
#else
#define HAVE___ATOMIC_FETCH_ADD 0
#endif
#endif
#ifndef HAVE___ATOMIC_FETCH_SUB
#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD
#endif
#ifndef DNS_ATOMIC_FETCH_ADD
#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_add available")
#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++)
#endif
#endif
#ifndef DNS_ATOMIC_FETCH_SUB
#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_sub available")
#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--)
#endif
#endif
static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_ADD(i);
} /* dns_atomic_fetch_add() */
static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_SUB(i);
} /* dns_atomic_fetch_sub() */
/*
* C R Y P T O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* P R N G
*/
#ifndef DNS_RANDOM
#if defined(HAVE_ARC4RANDOM) \
|| defined(__OpenBSD__) \
|| defined(__FreeBSD__) \
|| defined(__NetBSD__) \
|| defined(__APPLE__)
#define DNS_RANDOM arc4random
#elif __linux
#define DNS_RANDOM random
#else
#define DNS_RANDOM rand
#endif
#endif
#define DNS_RANDOM_arc4random 1
#define DNS_RANDOM_random 2
#define DNS_RANDOM_rand 3
#define DNS_RANDOM_RAND_bytes 4
#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM))
#if DNS_RANDOM_OPENSSL
#include <openssl/rand.h>
#endif
static unsigned dns_random_(void) {
#if DNS_RANDOM_OPENSSL
unsigned r;
_Bool ok;
ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r));
assert(ok && "1 == RAND_bytes()");
return r;
#else
return DNS_RANDOM();
#endif
} /* dns_random_() */
dns_random_f **dns_random_p(void) {
static dns_random_f *random_f = &dns_random_;
return &random_f;
} /* dns_random_p() */
/*
* P E R M U T A T I O N G E N E R A T O R
*/
#define DNS_K_TEA_KEY_SIZE 16
#define DNS_K_TEA_BLOCK_SIZE 8
#define DNS_K_TEA_CYCLES 32
#define DNS_K_TEA_MAGIC 0x9E3779B9U
struct dns_k_tea {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned cycles;
}; /* struct dns_k_tea */
static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) {
memcpy(tea->key, key, sizeof tea->key);
tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES;
} /* dns_k_tea_init() */
static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) {
uint32_t y, z, sum, n;
y = v[0];
z = v[1];
sum = 0;
for (n = 0; n < tea->cycles; n++) {
sum += DNS_K_TEA_MAGIC;
y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
}
w[0] = y;
w[1] = z;
return /* void */;
} /* dns_k_tea_encrypt() */
/*
* Permutation generator, based on a Luby-Rackoff Feistel construction.
*
* Specifically, this is a generic balanced Feistel block cipher using TEA
* (another block cipher) as the pseudo-random function, F. At best it's as
* strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
* perhaps Bernstein's Salsa20 core; I am naively trying to keep things
* simple.
*
* The generator can create a permutation of any set of numbers, as long as
* the size of the set is an even power of 2. This limitation arises either
* out of an inherent property of balanced Feistel constructions, or by my
* own ignorance. I'll tackle an unbalanced construction after I wrap my
* head around Schneier and Kelsey's paper.
*
* CAVEAT EMPTOR. IANAC.
*/
#define DNS_K_PERMUTOR_ROUNDS 8
struct dns_k_permutor {
unsigned stepi, length, limit;
unsigned shift, mask, rounds;
struct dns_k_tea tea;
}; /* struct dns_k_permutor */
static inline unsigned dns_k_permutor_powof(unsigned n) {
unsigned m, i = 0;
for (m = 1; m < n; m <<= 1, i++)
;;
return i;
} /* dns_k_permutor_powof() */
static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned width, i;
p->stepi = 0;
p->length = (high - low) + 1;
p->limit = high;
width = dns_k_permutor_powof(p->length);
width += width % 2;
p->shift = width / 2;
p->mask = (1U << p->shift) - 1;
p->rounds = DNS_K_PERMUTOR_ROUNDS;
for (i = 0; i < lengthof(key); i++)
key[i] = dns_random();
dns_k_tea_init(&p->tea, key, 0);
return /* void */;
} /* dns_k_permutor_init() */
static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) {
uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)];
memset(in, '\0', sizeof in);
in[0] = k;
in[1] = x;
dns_k_tea_encrypt(&p->tea, in, out);
return p->mask & out[0];
} /* dns_k_permutor_F() */
static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = 0;
l[i] = p->mask & (n >> p->shift);
r[i] = p->mask & (n >> 0);
do {
l[(i + 1) % 2] = r[i % 2];
r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
i++;
} while (i < p->rounds - 1);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_E() */
DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = p->rounds - 1;
l[i % 2] = p->mask & (n >> p->shift);
r[i % 2] = p->mask & (n >> 0);
do {
i--;
r[i % 2] = l[(i + 1) % 2];
l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
} while (i > 0);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_D() */
static unsigned dns_k_permutor_step(struct dns_k_permutor *p) {
unsigned n;
do {
n = dns_k_permutor_E(p, p->stepi++);
} while (n >= p->length);
return n + (p->limit + 1 - p->length);
} /* dns_k_permutor_step() */
/*
* Simple permutation box. Useful for shuffling rrsets from an iterator.
* Uses AES s-box to provide good diffusion.
*
* Seems to pass muster under runs test.
*
* $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done
* $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }'
* library(lawstat)
* runs.test(scan(file="/tmp/out"))
* EOF
*/
static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) {
static const unsigned char sbox[256] =
{ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
unsigned char a, b;
unsigned i;
a = 0xff & (n >> 0);
b = 0xff & (n >> 8);
for (i = 0; i < 4; i++) {
a ^= 0xff & s;
a = sbox[a] ^ b;
b = sbox[b] ^ a;
s >>= 8;
}
return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
} /* dns_k_shuffle16() */
/*
* S T A T E M A C H I N E R O U T I N E S
*
* Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the
* local variable pc.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_SM_ENTER \
do { \
static const int pc0 = __LINE__; \
DNS_SM_RESTORE; \
switch (pc0 + pc) { \
case __LINE__: (void)0
#define DNS_SM_SAVE_AND_DO(do_statement) \
do { \
pc = __LINE__ - pc0; \
DNS_SM_SAVE; \
do_statement; \
case __LINE__: (void)0; \
} while (0)
#define DNS_SM_YIELD(rv) \
DNS_SM_SAVE_AND_DO(return (rv))
#define DNS_SM_EXIT \
do { goto leave; } while (0)
#define DNS_SM_LEAVE \
leave: (void)0; \
DNS_SM_SAVE_AND_DO(break); \
} \
} while (0)
/*
* U T I L I T Y R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_MAXINTERVAL 300
struct dns_clock {
time_t sample, elapsed;
}; /* struct dns_clock */
static void dns_begin(struct dns_clock *clk) {
clk->sample = time(0);
clk->elapsed = 0;
} /* dns_begin() */
static time_t dns_elapsed(struct dns_clock *clk) {
time_t curtime;
if ((time_t)-1 == time(&curtime))
return clk->elapsed;
if (curtime > clk->sample)
clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL);
clk->sample = curtime;
return clk->elapsed;
} /* dns_elapsed() */
DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) {
size_t n = 0;
while (*src++ && n < m)
++n;
return n;
} /* dns_strnlen() */
DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) {
size_t len = dns_strnlen(src, max), n;
if (lim > 0) {
n = DNS_PP_MIN(lim - 1, len);
memcpy(dst, src, n);
dst[n] = '\0';
}
return len;
} /* dns_strnlcpy() */
#if (defined AF_UNIX && !defined _WIN32)
#define DNS_HAVE_SOCKADDR_UN 1
#else
#define DNS_HAVE_SOCKADDR_UN 0
#endif
static size_t dns_af_len(int af) {
static const size_t table[AF_MAX] = {
[AF_INET6] = sizeof (struct sockaddr_in6),
[AF_INET] = sizeof (struct sockaddr_in),
#if DNS_HAVE_SOCKADDR_UN
[AF_UNIX] = sizeof (struct sockaddr_un),
#endif
};
return table[af];
} /* dns_af_len() */
#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family)
#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa))
#define DNS_SA_NOPORT &dns_sa_noport
static unsigned short dns_sa_noport;
static unsigned short *dns_sa_port(int af, void *sa) {
switch (af) {
case AF_INET6:
return &((struct sockaddr_in6 *)sa)->sin6_port;
case AF_INET:
return &((struct sockaddr_in *)sa)->sin_port;
default:
return DNS_SA_NOPORT;
}
} /* dns_sa_port() */
static void *dns_sa_addr(int af, const void *sa, socklen_t *size) {
switch (af) {
case AF_INET6: {
struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
if (size)
*size = sizeof *in6;
return in6;
}
case AF_INET: {
struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr;
if (size)
*size = sizeof *in;
return in;
}
default:
if (size)
*size = 0;
return 0;
}
} /* dns_sa_addr() */
#if DNS_HAVE_SOCKADDR_UN
#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path)
#endif
DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) {
switch (dns_sa_family(sa)) {
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char *path = ((struct sockaddr_un *)sa)->sun_path;
if (size)
*size = dns_strnlen(path, DNS_SUNPATHMAX);
return path;
}
#endif
default:
if (size)
*size = 0;
return NULL;
}
} /* dns_sa_path() */
static int dns_sa_cmp(void *a, void *b) {
int cmp, af;
if ((cmp = dns_sa_family(a) - dns_sa_family(b)))
return cmp;
switch ((af = dns_sa_family(a))) {
case AF_INET: {
struct in_addr *a4, *b4;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a4 = dns_sa_addr(af, a, NULL);
b4 = dns_sa_addr(af, b, NULL);
if (ntohl(a4->s_addr) < ntohl(b4->s_addr))
return -1;
if (ntohl(a4->s_addr) > ntohl(b4->s_addr))
return 1;
return 0;
}
case AF_INET6: {
struct in6_addr *a6, *b6;
size_t i;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a6 = dns_sa_addr(af, a, NULL);
b6 = dns_sa_addr(af, b, NULL);
/* XXX: do we need to use in6_clearscope()? */
for (i = 0; i < sizeof a6->s6_addr; i++) {
if ((cmp = a6->s6_addr[i] - b6->s6_addr[i]))
return cmp;
}
return 0;
}
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path];
dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX);
dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX);
return strcmp(a_path, b_path);
}
#endif
default:
return -1;
}
} /* dns_sa_cmp() */
#if _WIN32
static int dns_inet_pton(int af, const void *src, void *dst) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
int size_of_u = (int)sizeof u;
u.sin.sin_family = af;
if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u))
return -1;
switch (af) {
case AF_INET6:
*(struct in6_addr *)dst = u.sin6.sin6_addr;
return 1;
case AF_INET:
*(struct in_addr *)dst = u.sin.sin_addr;
return 1;
default:
return 0;
}
} /* dns_inet_pton() */
static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
/* NOTE: WSAAddressToString will print .sin_port unless zeroed. */
memset(&u, 0, sizeof u);
u.sin.sin_family = af;
switch (af) {
case AF_INET6:
u.sin6.sin6_addr = *(struct in6_addr *)src;
break;
case AF_INET:
u.sin.sin_addr = *(struct in_addr *)src;
break;
default:
return 0;
}
if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim))
return 0;
return dst;
} /* dns_inet_ntop() */
#else
#define dns_inet_pton(...) inet_pton(__VA_ARGS__)
#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__)
#endif
static dns_error_t dns_pton(int af, const void *src, void *dst) {
switch (dns_inet_pton(af, src, dst)) {
case 1:
return 0;
case -1:
return dns_soerr();
default:
return DNS_EADDRESS;
}
} /* dns_pton() */
static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) {
return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr();
} /* dns_ntop() */
size_t dns_strlcpy(char *dst, const char *src, size_t lim) {
char *d = dst;
char *e = &dst[lim];
const char *s = src;
if (d < e) {
do {
if ('\0' == (*d++ = *s++))
return s - src - 1;
} while (d < e);
d[-1] = '\0';
}
while (*s++ != '\0')
;;
return s - src - 1;
} /* dns_strlcpy() */
size_t dns_strlcat(char *dst, const char *src, size_t lim) {
char *d = memchr(dst, '\0', lim);
char *e = &dst[lim];
const char *s = src;
const char *p;
if (d && d < e) {
do {
if ('\0' == (*d++ = *s++))
return d - dst - 1;
} while (d < e);
d[-1] = '\0';
}
p = s;
while (*s++ != '\0')
;;
return lim + (s - p - 1);
} /* dns_strlcat() */
static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) {
void *rp;
if (nmemb > 0 && SIZE_MAX / nmemb < size) {
*error = EOVERFLOW;
return NULL;
}
if (!(rp = realloc(p, nmemb * size)))
*error = (errno)? errno : EINVAL;
return rp;
} /* dns_reallocarray() */
#if _WIN32
static char *dns_strsep(char **sp, const char *delim) {
char *p;
if (!(p = *sp))
return 0;
*sp += strcspn(p, delim);
if (**sp != '\0') {
**sp = '\0';
++*sp;
} else
*sp = NULL;
return p;
} /* dns_strsep() */
#else
#define dns_strsep(...) strsep(__VA_ARGS__)
#endif
#if _WIN32
#define strcasecmp(...) _stricmp(__VA_ARGS__)
#define strncasecmp(...) _strnicmp(__VA_ARGS__)
#endif
static inline _Bool dns_isalpha(unsigned char c) {
return isalpha(c);
} /* dns_isalpha() */
static inline _Bool dns_isdigit(unsigned char c) {
return isdigit(c);
} /* dns_isdigit() */
static inline _Bool dns_isalnum(unsigned char c) {
return isalnum(c);
} /* dns_isalnum() */
static inline _Bool dns_isspace(unsigned char c) {
return isspace(c);
} /* dns_isspace() */
static inline _Bool dns_isgraph(unsigned char c) {
return isgraph(c);
} /* dns_isgraph() */
static int dns_poll(int fd, short events, int timeout) {
fd_set rset, wset;
struct timeval tv = { timeout, 0 };
if (!events)
return 0;
if (fd < 0 || (unsigned)fd >= FD_SETSIZE)
return EINVAL;
FD_ZERO(&rset);
FD_ZERO(&wset);
if (events & DNS_POLLIN)
FD_SET(fd, &rset);
if (events & DNS_POLLOUT)
FD_SET(fd, &wset);
select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL);
return 0;
} /* dns_poll() */
#if !_WIN32
DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) {
#if DNS_THREAD_SAFE
return pthread_sigmask(how, set, oset);
#else
return (0 == sigprocmask(how, set, oset))? 0 : errno;
#endif
} /* dns_sigmask() */
#endif
static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) {
long n = send(fd, src, len, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else {
*error = 0;
return n;
}
} /* dns_send() */
static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) {
long n = recv(fd, dst, lim, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else if (n == 0) {
*error = (lim > 0)? DNS_ECONNFIN : EINVAL;
return 0;
} else {
*error = 0;
return n;
}
} /* dns_recv() */
static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) {
#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE
return dns_send(fd, src, len, flags, _error);
#elif defined MSG_NOSIGNAL
return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error);
#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */
/*
* SIGPIPE handling similar to the approach described in
* http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html
*/
sigset_t pending, blocked, piped;
size_t count;
int error;
sigemptyset(&pending);
sigpending(&pending);
if (!sigismember(&pending, SIGPIPE)) {
sigemptyset(&piped);
sigaddset(&piped, SIGPIPE);
sigemptyset(&blocked);
if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked)))
goto error;
}
count = dns_send(fd, src, len, flags, &error);
if (!sigismember(&pending, SIGPIPE)) {
int saved = error;
const struct timespec ts = { 0, 0 };
if (!count && error == EPIPE) {
while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR)
;;
}
if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL)))
goto error;
error = saved;
}
*_error = error;
return count;
error:
*_error = error;
return 0;
#else
#error "unable to suppress SIGPIPE"
return dns_send(fd, src, len, flags, _error);
#endif
} /* dns_send_nopipe() */
static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) {
if (0 != connect(fd, addr, addrlen))
return dns_soerr();
return 0;
} /* dns_connect() */
#define DNS_FOPEN_STDFLAGS "rwabt+"
static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) {
char *p = dst, *pe = dst + lim;
/* copy standard flags */
while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) {
if (!(p < pe))
return ENOMEM;
*p++ = *src++;
}
/* append flag to standard flags */
if (!(p < pe))
return ENOMEM;
*p++ = fc;
/* copy remaining mode string, including '\0' */
do {
if (!(p < pe))
return ENOMEM;
} while ((*p++ = *src++));
return 0;
} /* dns_fopen_addflag() */
static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) {
FILE *fp;
char mode_cloexec[32];
int error;
assert(path && mode && *mode);
if (!*path) {
error = EINVAL;
goto error;
}
#if _WIN32 || _WIN64
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N')))
goto error;
if (!(fp = fopen(path, mode_cloexec)))
goto syerr;
#else
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e')))
goto error;
if (!(fp = fopen(path, mode_cloexec))) {
if (errno != EINVAL)
goto syerr;
if (!(fp = fopen(path, mode)))
goto syerr;
}
#endif
return fp;
syerr:
error = dns_syerr();
error:
*_error = error;
return NULL;
} /* dns_fopen() */
struct dns_hxd_lines_i {
int pc;
size_t p;
};
#define DNS_SM_RESTORE \
do { \
pc = state->pc; \
sp = src + state->p; \
se = src + len; \
} while (0)
#define DNS_SM_SAVE \
do { \
state->p = sp - src; \
state->pc = pc; \
} while (0)
static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) {
static const unsigned char hex[] = "0123456789abcdef";
static const unsigned char tmpl[] = " | |\n";
unsigned char ln[sizeof tmpl];
const unsigned char *sp, *se;
unsigned char *h, *g;
unsigned i, n;
int pc;
DNS_SM_ENTER;
while (sp < se) {
memcpy(ln, tmpl, sizeof ln);
h = &ln[2];
g = &ln[53];
for (n = 0; n < 2; n++) {
for (i = 0; i < 8 && se - sp > 0; i++, sp++) {
h[0] = hex[0x0f & (*sp >> 4)];
h[1] = hex[0x0f & (*sp >> 0)];
h += 3;
*g++ = (dns_isgraph(*sp))? *sp : '.';
}
h++;
}
n = dns_strlcpy(dst, (char *)ln, lim);
DNS_SM_YIELD(n);
}
DNS_SM_EXIT;
DNS_SM_LEAVE;
return 0;
}
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
/*
* A R I T H M E T I C R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_CHECK_OVERFLOW(error, r, f, ...) \
do { \
uintmax_t _r; \
*(error) = f(&_r, __VA_ARGS__); \
*(r) = _r; \
} while (0)
static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) {
if (n > clamp) {
*r = clamp;
return ERANGE;
} else {
*r = n;
return 0;
}
} /* dns_clamp_overflow() */
static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (~a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a + b, clamp);
}
} /* dns_add_overflow() */
static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (a > 0 && UINTMAX_MAX / a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a * b, clamp);
}
} /* dns_mul_overflow() */
/*
* F I X E D - S I Z E D B U F F E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_B_INIT(src, n) { \
(unsigned char *)(src), \
(unsigned char *)(src), \
(unsigned char *)(src) + (n), \
}
#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n))
#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n))
struct dns_buf {
const unsigned char *base;
unsigned char *p;
const unsigned char *pe;
dns_error_t error;
size_t overflow;
}; /* struct dns_buf */
static inline size_t
dns_b_tell(struct dns_buf *b)
{
return b->p - b->base;
}
static inline dns_error_t
dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error)
{
b->overflow += n;
return b->error = error;
}
DNS_NOTUSED static struct dns_buf *
dns_b_into(struct dns_buf *b, void *src, size_t n)
{
*b = (struct dns_buf)DNS_B_INTO(src, n);
return b;
}
static dns_error_t
dns_b_putc(struct dns_buf *b, unsigned char uc)
{
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_ENOBUFS);
*b->p++ = uc;
return 0;
}
static dns_error_t
dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p)
{
size_t pe = b->pe - b->base;
if (pe <= p)
return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS);
*((unsigned char *)b->base + p) = uc;
return 0;
}
static inline dns_error_t
dns_b_put16(struct dns_buf *b, uint16_t u)
{
return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static inline dns_error_t
dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p)
{
if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1))
return b->error;
return 0;
}
DNS_NOTUSED static inline dns_error_t
dns_b_put32(struct dns_buf *b, uint32_t u)
{
return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16),
dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static dns_error_t
dns_b_put(struct dns_buf *b, const void *src, size_t len)
{
size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len);
memcpy(b->p, src, n);
b->p += n;
if (n < len)
return dns_b_setoverflow(b, len - n, DNS_ENOBUFS);
return 0;
}
static dns_error_t
dns_b_puts(struct dns_buf *b, const void *src)
{
return dns_b_put(b, src, strlen(src));
}
DNS_NOTUSED static inline dns_error_t
dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width)
{
size_t digits, padding, overflow;
uintmax_t r;
unsigned char *tp, *te, tc;
digits = 0;
r = u;
do {
digits++;
r /= 10;
} while (r);
padding = width - DNS_PP_MIN(digits, width);
overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding));
while (padding--) {
dns_b_putc(b, '0');
}
digits = 0;
tp = b->p;
r = u;
do {
if (overflow < ++digits)
dns_b_putc(b, '0' + (r % 10));
r /= 10;
} while (r);
te = b->p;
while (tp < te) {
tc = *--te;
*te = *tp;
*tp++ = tc;
}
return b->error;
}
static void
dns_b_popc(struct dns_buf *b)
{
if (b->overflow && !--b->overflow)
b->error = 0;
if (b->p > b->base)
b->p--;
}
static inline const char *
dns_b_tolstring(struct dns_buf *b, size_t *n)
{
if (b->p < b->pe) {
*b->p = '\0';
*n = b->p - b->base;
return (const char *)b->base;
} else if (b->p > b->base) {
if (b->p[-1] != '\0') {
dns_b_setoverflow(b, 1, DNS_ENOBUFS);
b->p[-1] = '\0';
}
*n = &b->p[-1] - b->base;
return (const char *)b->base;
} else {
*n = 0;
return "";
}
}
static inline const char *
dns_b_tostring(struct dns_buf *b)
{
size_t n;
return dns_b_tolstring(b, &n);
}
static inline size_t
dns_b_strlen(struct dns_buf *b)
{
size_t n;
dns_b_tolstring(b, &n);
return n;
}
static inline size_t
dns_b_strllen(struct dns_buf *b)
{
size_t n = dns_b_strlen(b);
return n + b->overflow;
}
DNS_NOTUSED static const struct dns_buf *
dns_b_from(const struct dns_buf *b, const void *src, size_t n)
{
*(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n);
return b;
}
static inline int
dns_b_getc(const struct dns_buf *_b, const int eof)
{
struct dns_buf *b = (struct dns_buf *)_b;
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof;
return *b->p++;
}
static inline intmax_t
dns_b_get16(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_getc(b, 0) << 8);
n |= (dns_b_getc(b, 0) << 0);
return (!b->overflow)? n : eof;
}
DNS_NOTUSED static inline intmax_t
dns_b_get32(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_get16(b, 0) << 16);
n |= (dns_b_get16(b, 0) << 0);
return (!b->overflow)? n : eof;
}
static inline dns_error_t
dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n)
{
struct dns_buf *src = (struct dns_buf *)_src;
size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n);
size_t src_r = n - src_n;
dns_b_put(dst, src->p, src_n);
src->p += src_n;
if (src_r)
return dns_b_setoverflow(src, src_r, DNS_EILLEGAL);
return dst->error;
}
/*
* T I M E R O U T I N E S
*
* Most functions still rely on the older time routines defined in the
* utility routines section, above.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_TIME_C(n) UINT64_C(n)
#define DNS_TIME_INF (~DNS_TIME_C(0))
typedef uint64_t dns_time_t;
typedef dns_time_t dns_microseconds_t;
static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) {
if (a < b) {
*r = DNS_TIME_C(0);
return ERANGE;
} else {
*r = a - b;
return 0;
}
}
static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) {
if (ts) {
dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec);
dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec);
dns_time_t usec = nsec / 1000;
dns_microseconds_t r;
if (rup && nsec % 1000 > 0)
usec++;
dns_time_mul(&r, sec, DNS_TIME_C(1000000));
dns_time_add(&r, r, usec);
return r;
} else {
return DNS_TIME_INF;
}
} /* dns_ts2us() */
static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) {
if (tv) {
ts->tv_sec = tv->tv_sec;
ts->tv_nsec = tv->tv_usec * 1000;
return ts;
} else {
return NULL;
}
} /* dns_tv2ts() */
static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, us / 1000000, 1);
dns_b_putc(&dst, '.');
dns_b_fmtju(&dst, us % 1000000, 6);
return dns_b_strllen(&dst);
} /* dns_utime_print() */
/*
* P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
unsigned dns_p_count(struct dns_packet *P, enum dns_section section) {
unsigned count;
switch (section) {
case DNS_S_QD:
return ntohs(dns_header(P)->qdcount);
case DNS_S_AN:
return ntohs(dns_header(P)->ancount);
case DNS_S_NS:
return ntohs(dns_header(P)->nscount);
case DNS_S_AR:
return ntohs(dns_header(P)->arcount);
default:
count = 0;
if (section & DNS_S_QD)
count += ntohs(dns_header(P)->qdcount);
if (section & DNS_S_AN)
count += ntohs(dns_header(P)->ancount);
if (section & DNS_S_NS)
count += ntohs(dns_header(P)->nscount);
if (section & DNS_S_AR)
count += ntohs(dns_header(P)->arcount);
return count;
}
} /* dns_p_count() */
struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) {
if (!P)
return 0;
assert(size >= offsetof(struct dns_packet, data) + 12);
memset(P, 0, sizeof *P);
P->size = size - offsetof(struct dns_packet, data);
P->end = 12;
memset(P->data, '\0', 12);
return P;
} /* dns_p_init() */
static struct dns_packet *dns_p_reset(struct dns_packet *P) {
return dns_p_init(P, offsetof(struct dns_packet, data) + P->size);
} /* dns_p_reset() */
static unsigned short dns_p_qend(struct dns_packet *P) {
unsigned short qend = 12;
unsigned i, count = dns_p_count(P, DNS_S_QD);
for (i = 0; i < count && qend < P->end; i++) {
if (P->end == (qend = dns_d_skip(qend, P)))
goto invalid;
if (P->end - qend < 4)
goto invalid;
qend += 4;
}
return DNS_PP_MIN(qend, P->end);
invalid:
return P->end;
} /* dns_p_qend() */
struct dns_packet *dns_p_make(size_t len, int *error) {
struct dns_packet *P;
size_t size = dns_p_calcsize(len);
if (!(P = dns_p_init(malloc(size), size)))
*error = dns_syerr();
return P;
} /* dns_p_make() */
static void dns_p_free(struct dns_packet *P) {
free(P);
} /* dns_p_free() */
/* convenience routine to free any existing packet before storing new packet */
static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) {
dns_p_free(*dst);
*dst = src;
return src;
} /* dns_p_setptr() */
static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) {
dns_p_setptr(dst, *src);
*src = NULL;
return *dst;
} /* dns_p_movptr() */
int dns_p_grow(struct dns_packet **P) {
struct dns_packet *tmp;
size_t size;
int error;
if (!*P) {
if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error)))
return error;
return 0;
}
size = dns_p_sizeof(*P);
size |= size >> 1;
size |= size >> 2;
size |= size >> 4;
size |= size >> 8;
size++;
if (size > 65536)
return DNS_ENOBUFS;
if (!(tmp = realloc(*P, dns_p_calcsize(size))))
return dns_syerr();
tmp->size = size;
*P = tmp;
return 0;
} /* dns_p_grow() */
struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) {
if (!P)
return 0;
P->end = DNS_PP_MIN(P->size, P0->end);
memcpy(P->data, P0->data, P->end);
return P;
} /* dns_p_copy() */
struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) {
size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0));
struct dns_packet *M;
enum dns_section section;
struct dns_rr rr, mr;
int error, copy;
if (!A && B) {
A = B;
Amask = Bmask;
B = 0;
}
merge:
if (!(M = dns_p_make(bufsiz, &error)))
goto error;
for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) {
if (A && (section & Amask)) {
dns_rr_foreach(&rr, A, .section = section) {
if ((error = dns_rr_copy(M, &rr, A)))
goto error;
}
}
if (B && (section & Bmask)) {
dns_rr_foreach(&rr, B, .section = section) {
copy = 1;
dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) {
if (!(copy = dns_rr_cmp(&rr, B, &mr, M)))
break;
}
if (copy && (error = dns_rr_copy(M, &rr, B)))
goto error;
}
}
}
return M;
error:
dns_p_setptr(&M, NULL);
if (error == DNS_ENOBUFS && bufsiz < 65535) {
bufsiz = DNS_PP_MIN(65535, bufsiz * 2);
goto merge;
}
*error_ = error;
return 0;
} /* dns_p_merge() */
static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t);
void dns_p_dictadd(struct dns_packet *P, unsigned short dn) {
unsigned short lp, lptr, i;
lp = dn;
while (lp < P->end) {
if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) {
lptr = ((0x3f & P->data[lp + 0]) << 8)
| ((0xff & P->data[lp + 1]) << 0);
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
if (P->dict[i] == lptr) {
P->dict[i] = dn;
return;
}
}
}
lp = dns_l_skip(lp, P->data, P->end);
}
for (i = 0; i < lengthof(P->dict); i++) {
if (!P->dict[i]) {
P->dict[i] = dn;
break;
}
}
} /* dns_p_dictadd() */
static inline uint16_t
plus1_ns (uint16_t count_net)
{
uint16_t count = ntohs (count_net);
count++;
return htons (count);
}
int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) {
size_t end = P->end;
int error;
if ((error = dns_d_push(P, dn, dnlen)))
goto error;
if (P->size - P->end < 4)
goto nobufs;
P->data[P->end++] = 0xff & (type >> 8);
P->data[P->end++] = 0xff & (type >> 0);
P->data[P->end++] = 0xff & (class >> 8);
P->data[P->end++] = 0xff & (class >> 0);
if (section == DNS_S_QD)
goto update;
if (P->size - P->end < 6)
goto nobufs;
if (type != DNS_T_OPT)
ttl = DNS_PP_MIN(ttl, 0x7fffffffU);
P->data[P->end++] = ttl >> 24;
P->data[P->end++] = ttl >> 16;
P->data[P->end++] = ttl >> 8;
P->data[P->end++] = ttl >> 0;
if ((error = dns_any_push(P, (union dns_any *)any, type)))
goto error;
update:
switch (section) {
case DNS_S_QD:
if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.qd.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount);
P->memo.qd.end = P->end;
P->memo.an.base = P->end;
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AN:
if (dns_p_count(P, DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.an.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount);
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_NS:
if (dns_p_count(P, DNS_S_AR))
goto order;
if (!P->memo.ns.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount);
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AR:
if (!P->memo.ar.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount);
P->memo.ar.end = P->end;
if (type == DNS_T_OPT && !P->memo.opt.p) {
P->memo.opt.p = end;
P->memo.opt.maxudp = class;
P->memo.opt.ttl = ttl;
}
break;
default:
error = DNS_ESECTION;
goto error;
} /* switch() */
return 0;
nobufs:
error = DNS_ENOBUFS;
goto error;
order:
error = DNS_EORDER;
goto error;
error:
P->end = end;
return error;
} /* dns_p_push() */
#define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0)
#define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0)
struct dns_p_lines_i {
int pc;
enum dns_section section;
struct dns_rr rr;
int error;
};
static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) {
va_list ap;
int error = 0, n;
va_start(ap, fmt);
if ((n = vsnprintf(dst, lim, fmt, ap)) < 0)
error = errno;
va_end(ap);
*_error = error;
return DNS_PP_MAX(n, 0);
} /* dns_p_lines_fmt() */
#define DNS_P_LINE(...) \
do { \
len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \
if (len == 0 && error) \
goto error; \
DNS_SM_YIELD(len); \
} while (0)
static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) {
int error, pc;
size_t len;
*_error = 0;
DNS_SM_ENTER;
DNS_P_LINE(";; [HEADER]\n");
DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid));
DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
while (dns_rr_grep(&state->rr, 1, I, P, &error)) {
if (state->section != state->rr.section) {
DNS_P_LINE("\n");
DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section), dns_p_count(P, state->rr.section));
}
if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error)))
goto error;
dns_strlcat(dst, "\n", lim);
DNS_SM_YIELD(len + 1);
state->section = state->rr.section;
}
if (error)
goto error;
DNS_SM_EXIT;
error:
for (;;) {
*_error = error;
DNS_SM_YIELD(0);
}
DNS_SM_LEAVE;
*_error = 0;
return 0;
} /* dns_p_lines() */
#undef DNS_P_LINE
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) {
struct dns_p_lines_i lines = { 0 };
char line[sizeof (union dns_any) * 2];
size_t len;
int error;
while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) {
if (len < sizeof line) {
fwrite(line, 1, len, fp);
} else {
fwrite(line, 1, sizeof line - 1, fp);
fputc('\n', fp);
}
}
} /* dns_p_dump3() */
void dns_p_dump(struct dns_packet *P, FILE *fp) {
struct dns_rr_i I_instance = { 0 };
dns_p_dump3(P, &I_instance, fp);
} /* dns_p_dump() */
static void dns_s_unstudy(struct dns_s_memo *m)
{ m->base = 0; m->end = 0; }
static void dns_m_unstudy(struct dns_p_memo *m) {
dns_s_unstudy(&m->qd);
dns_s_unstudy(&m->an);
dns_s_unstudy(&m->ns);
dns_s_unstudy(&m->ar);
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
} /* dns_m_unstudy() */
static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) {
unsigned short count, rp;
count = dns_p_count(P, section);
for (rp = base; count && rp < P->end; count--)
rp = dns_rr_skip(rp, P);
m->base = base;
m->end = rp;
return 0;
} /* dns_s_study() */
static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P)))
goto error;
if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P)))
goto error;
if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P)))
goto error;
if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P)))
goto error;
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) {
m->opt.p = rr.dn.p;
m->opt.maxudp = rr.class;
m->opt.ttl = rr.ttl;
break;
}
return 0;
error:
dns_m_unstudy(m);
return error;
} /* dns_m_study() */
int dns_p_study(struct dns_packet *P) {
return dns_m_study(&P->memo, P);
} /* dns_p_study() */
enum dns_rcode dns_p_rcode(struct dns_packet *P) {
return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode);
} /* dns_p_rcode() */
/*
* Q U E R Y P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_Q_RD 0x1 /* recursion desired */
#define DNS_Q_EDNS0 0x2 /* include OPT RR */
static dns_error_t
dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags)
{
struct dns_packet *Q = NULL;
int error;
if (dns_p_movptr(&Q, _Q)) {
dns_p_reset(Q);
} else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) {
goto error;
}
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0)))
goto error;
dns_header(Q)->rd = !!(qflags & DNS_Q_RD);
if (qflags & DNS_Q_EDNS0) {
struct dns_opt opt = DNS_OPT_INIT(&opt);
opt.version = 0; /* RFC 6891 version */
opt.maxudp = 4096;
if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt)))
goto error;
}
*_Q = Q;
return 0;
error:
dns_p_free(Q);
return error;
}
static dns_error_t
dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags)
{
return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags);
}
static dns_error_t
dns_q_remake(struct dns_packet **Q, int qflags)
{
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error;
assert(Q && *Q);
if ((error = dns_rr_parse(&rr, 12, *Q)))
return error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error)))
return error;
if (qlen >= sizeof qname)
return DNS_EILLEGAL;
return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags);
}
/*
* D O M A I N N A M E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DNS_D_MAXPTRS
#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */
#endif
static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) {
unsigned short len;
unsigned nptrs = 0;
retry:
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
if (lim > 0) {
memcpy(dst, &data[src], DNS_PP_MIN(lim, len));
dst[DNS_PP_MIN(lim - 1, len)] = '\0';
}
*nxt = src + len;
return len;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
if (++nptrs > DNS_D_MAXPTRS)
goto invalid;
if (end - src < 2)
goto invalid;
src = ((0x3f & data[src + 0]) << 8)
| ((0xff & data[src + 1]) << 0);
goto retry;
} /* switch() */
/* NOT REACHED */
invalid:
*nxt = end;
return 0;
} /* dns_l_expand() */
static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) {
unsigned short len;
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
return (len)? src + len : end;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
return end;
} /* switch() */
/* NOT REACHED */
invalid:
return end;
} /* dns_l_skip() */
static _Bool dns_d_isanchored(const void *_src, size_t len) {
const unsigned char *src = _src;
return len > 0 && src[len - 1] == '.';
} /* dns_d_isanchored() */
static size_t dns_d_ndots(const void *_src, size_t len) {
const unsigned char *p = _src, *pe = p + len;
size_t ndots = 0;
while ((p = memchr(p, '.', pe - p))) {
ndots++;
p++;
}
return ndots;
} /* dns_d_ndots() */
static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) {
unsigned char *dst = dst_;
const unsigned char *src = src_;
size_t dp = 0, sp = 0;
int lc;
/* trim any leading dot(s) */
while (sp < len && src[sp] == '.')
sp++;
for (lc = 0; sp < len; lc = src[sp++]) {
/* trim extra dot(s) */
if (src[sp] == '.' && lc == '.')
continue;
if (dp < lim)
dst[dp] = src[sp];
dp++;
}
if ((flags & DNS_D_ANCHOR) && lc != '.') {
if (dp < lim)
dst[dp] = '.';
dp++;
}
if (lim > 0)
dst[DNS_PP_MIN(dp, lim - 1)] = '\0';
return dp;
} /* dns_d_trim() */
char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) {
if (flags & DNS_D_TRIM) {
dns_d_trim(dst, lim, src, len, flags);
} if (flags & DNS_D_ANCHOR) {
dns_d_anchor(dst, lim, src, len);
} else {
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0';
}
return dst;
} /* dns_d_init() */
size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) {
if (len == 0)
return 0;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (((const char *)src)[len - 1] != '.') {
if (len < lim)
((char *)dst)[len] = '.';
len++;
}
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_anchor() */
size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) {
const char *dot;
/* XXX: Skip any leading dot. Handles cleaving root ".". */
if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1)))
return 0;
len -= dot - (const char *)src;
/* XXX: Unless root, skip the label's trailing dot. */
if (len > 1) {
src = ++dot;
len--;
} else
src = dot;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_cleave() */
size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) {
struct { unsigned char *b; size_t p, x; } dst, src;
unsigned char ch = '.';
dst.b = dst_;
dst.p = 0;
dst.x = 1;
src.b = (unsigned char *)src_;
src.p = 0;
src.x = 0;
while (src.x < len) {
ch = src.b[src.x];
if (ch == '.') {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x++;
src.p = ++src.x;
} else {
if (dst.x < lim)
dst.b[dst.x] = ch;
dst.x++;
src.x++;
}
} /* while() */
if (src.x > src.p) {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x;
}
if (dst.p > 1) {
if (dst.p < lim)
dst.b[dst.p] = 0x00;
dst.p++;
}
#if 1
if (dst.p < lim) {
struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b;
unsigned i;
a.p = 0;
while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) {
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
b.p = P->dict[i];
while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) {
a.y = a.x;
b.y = b.x;
while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) {
a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim);
b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end);
}
if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) {
dst.b[a.p++] = 0xc0
| (0x3f & (b.p >> 8));
dst.b[a.p++] = (0xff & (b.p >> 0));
/* silence static analyzers */
dns_assume(a.p > 0);
return a.p;
}
b.p = b.x;
} /* while() */
} /* for() */
a.p = a.x;
} /* while() */
} /* if () */
#endif
if (!dst.p)
*error = DNS_EILLEGAL;
return dst.p;
} /* dns_d_comp() */
unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) {
unsigned short len;
while (src < P->end) {
switch (0x03 & (P->data[src] >> 6)) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src++]);
if (0 == len) {
/* success ==> */ return src;
} else if (P->end - src > len) {
src += len;
break;
} else
goto invalid;
/* NOT REACHED */
case 0x01: /* RESERVED */
goto invalid;
case 0x02: /* RESERVED */
goto invalid;
case 0x03: /* POINTER */
if (P->end - src < 2)
goto invalid;
src += 2;
/* success ==> */ return src;
} /* switch() */
} /* while() */
invalid:
return P->end;
} /* dns_d_skip() */
#include <stdio.h>
size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) {
size_t dstp = 0;
unsigned nptrs = 0;
unsigned char len;
while (src < P->end) {
switch ((0x03 & (P->data[src] >> 6))) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src]);
if (0 == len) {
if (dstp == 0) {
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
}
/* NUL terminate */
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
/* success ==> */ return dstp;
}
src++;
if (P->end - src < len)
goto toolong;
if (dstp < lim)
memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp));
src += len;
dstp += len;
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
nptrs = 0;
continue;
case 0x01: /* RESERVED */
goto reserved;
case 0x02: /* RESERVED */
goto reserved;
case 0x03: /* POINTER */
if (++nptrs > DNS_D_MAXPTRS)
goto toolong;
if (P->end - src < 2)
goto toolong;
src = ((0x3f & P->data[src + 0]) << 8)
| ((0xff & P->data[src + 1]) << 0);
continue;
} /* switch() */
} /* while() */
toolong:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
reserved:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
} /* dns_d_expand() */
int dns_d_push(struct dns_packet *P, const void *dn, size_t len) {
size_t lim = P->size - P->end;
unsigned dp = P->end;
int error = DNS_EILLEGAL; /* silence compiler */
len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error);
if (len == 0)
return error;
if (len > lim)
return DNS_ENOBUFS;
P->end += len;
dns_p_dictadd(P, dp);
return 0;
} /* dns_d_push() */
size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) {
char host[DNS_D_MAXNAME + 1];
struct dns_rr_i i;
struct dns_rr rr;
unsigned depth;
int error;
if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len))
{ error = ENAMETOOLONG; goto error; }
for (depth = 0; depth < 7; depth++) {
memset(&i, 0, sizeof i);
i.section = DNS_S_ALL & ~DNS_S_QD;
i.name = host;
i.type = DNS_T_CNAME;
if (!dns_rr_grep(&rr, 1, &i, P, &error))
break;
if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P)))
goto error;
}
return dns_strlcpy(dst, host, lim);
error:
*error_ = error;
return 0;
} /* dns_d_cname() */
/*
* R E S O U R C E R E C O R D R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) {
unsigned char dn[DNS_D_MAXNAME + 1];
union dns_any any;
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error)))
return error;
else if (len >= sizeof dn)
return DNS_EILLEGAL;
if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q)))
return error;
return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any);
} /* dns_rr_copy() */
int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) {
unsigned short p = src;
if (src >= P->end)
goto invalid;
rr->dn.p = p;
rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p;
if (P->end - p < 4)
goto invalid;
rr->type = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->class = ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
p += 4;
if (src < dns_p_qend(P)) {
rr->section = DNS_S_QUESTION;
rr->ttl = 0;
rr->rd.p = 0;
rr->rd.len = 0;
return 0;
}
if (P->end - p < 4)
goto invalid;
rr->ttl = ((0xff & P->data[p + 0]) << 24)
| ((0xff & P->data[p + 1]) << 16)
| ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
if (rr->type != DNS_T_OPT)
rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU);
p += 4;
if (P->end - p < 2)
goto invalid;
rr->rd.len = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->rd.p = p + 2;
p += 2;
if (P->end - p < rr->rd.len)
goto invalid;
return 0;
invalid:
return DNS_EILLEGAL;
} /* dns_rr_parse() */
static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) {
unsigned short rp, rdlen;
rp = dns_d_skip(src, P);
if (P->end - rp < 4)
return P->end - src;
rp += 4; /* TYPE, CLASS */
if (rp <= dns_p_qend(P))
return rp - src;
if (P->end - rp < 6)
return P->end - src;
rp += 6; /* TTL, RDLEN */
rdlen = ((0xff & P->data[rp - 2]) << 8)
| ((0xff & P->data[rp - 1]) << 0);
if (P->end - rp < rdlen)
return P->end - src;
rp += rdlen;
return rp - src;
} /* dns_rr_len() */
unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) {
return src + dns_rr_len(src, P);
} /* dns_rr_skip() */
static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) {
enum dns_section section;
unsigned count, index;
unsigned short rp;
if (src >= P->memo.qd.base && src < P->memo.qd.end)
return DNS_S_QD;
if (src >= P->memo.an.base && src < P->memo.an.end)
return DNS_S_AN;
if (src >= P->memo.ns.base && src < P->memo.ns.end)
return DNS_S_NS;
if (src >= P->memo.ar.base && src < P->memo.ar.end)
return DNS_S_AR;
/* NOTE: Possibly bad memoization. Try it the hard-way. */
for (rp = 12, index = 0; rp < src && rp < P->end; index++)
rp = dns_rr_skip(rp, P);
section = DNS_S_QD;
count = dns_p_count(P, section);
while (index >= count && section <= DNS_S_AR) {
section <<= 1;
count += dns_p_count(P, section);
}
return DNS_S_ALL & section;
} /* dns_rr_section() */
static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_rr_parse(&rr, src, P)))
return 0;
return rr.type;
} /* dns_rr_type() */
int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) {
char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1];
union dns_any any0, any1;
int cmp, error;
size_t len;
if ((cmp = r0->type - r1->type))
return cmp;
if ((cmp = r0->class - r1->class))
return cmp;
/*
* FIXME: Do label-by-label comparison to handle illegally long names?
*/
if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error))
|| len >= sizeof host0)
return -1;
if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error))
|| len >= sizeof host1)
return 1;
if ((cmp = strcasecmp(host0, host1)))
return cmp;
if (DNS_S_QD & (r0->section | r1->section)) {
if (r0->section == r1->section)
return 0;
return (r0->section == DNS_S_QD)? -1 : 1;
}
if ((error = dns_any_parse(&any0, r0, P0)))
return -1;
if ((error = dns_any_parse(&any1, r1, P1)))
return 1;
return dns_any_cmp(&any0, r0->type, &any1, r1->type);
} /* dns_rr_cmp() */
static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) {
struct dns_rr rr1;
dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) {
if (0 == dns_rr_cmp(rr0, P0, &rr1, P1))
return 1;
}
return 0;
} /* dns_rr_exists() */
static unsigned short dns_rr_offset(struct dns_rr *rr) {
return rr->dn.p;
} /* dns_rr_offset() */
static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) {
if (i->section && !(rr->section & i->section))
return 0;
if (i->type && rr->type != i->type && i->type != DNS_T_ALL)
return 0;
if (i->class && rr->class != i->class && i->class != DNS_C_ANY)
return 0;
if (i->name) {
char dn[DNS_D_MAXNAME + 1];
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error))
|| len >= sizeof dn)
return 0;
if (0 != strcasecmp(dn, i->name))
return 0;
}
if (i->data && i->type && rr->section > DNS_S_QD) {
union dns_any rd;
int error;
if ((error = dns_any_parse(&rd, rr, P)))
return 0;
if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type))
return 0;
}
return 1;
} /* dns_rr_i_match() */
static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) {
unsigned short rp;
struct dns_rr r0, rr;
int error;
if ((i->section & DNS_S_QD) && P->memo.qd.base)
rp = P->memo.qd.base;
else if ((i->section & DNS_S_AN) && P->memo.an.base)
rp = P->memo.an.base;
else if ((i->section & DNS_S_NS) && P->memo.ns.base)
rp = P->memo.ns.base;
else if ((i->section & DNS_S_AR) && P->memo.ar.base)
rp = P->memo.ar.base;
else
rp = 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
r0 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r0);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) < 0)
r0 = rr;
}
return dns_rr_offset(&r0);
} /* dns_rr_i_start() */
static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) {
struct dns_rr r0, r1, rr;
int error;
if ((error = dns_rr_parse(&r0, rp, P)))
return P->end;
r0.section = dns_rr_section(rp, P);
rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
r1 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r1);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
if (i->sort(&rr, &r1, i, P) >= 0)
continue;
r1 = rr;
}
return dns_rr_offset(&r1);
} /* dns_rr_i_skip() */
int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
(void)i;
(void)P;
return (int)a->dn.p - (int)b->dn.p;
} /* dns_rr_i_packet() */
int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
if ((cmp = a->section - b->section))
return cmp;
if (a->type != b->type)
return (int)a->dn.p - (int)b->dn.p;
return dns_rr_cmp(a, P, b, P);
} /* dns_rr_i_order() */
int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
(void)P;
while (!i->state.regs[0])
i->state.regs[0] = dns_random();
if ((cmp = a->section - b->section))
return cmp;
return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]);
} /* dns_rr_i_shuffle() */
void dns_rr_i_init(struct dns_rr_i *i) {
static const struct dns_rr_i i_initializer;
i->state = i_initializer.state;
i->saved = i->state;
} /* dns_rr_i_init() */
unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) {
unsigned count = 0;
int error;
switch (i->state.exec) {
case 0:
if (!i->sort)
i->sort = &dns_rr_i_packet;
i->state.next = dns_rr_i_start(i, P);
i->state.exec++;
/* FALL THROUGH */
case 1:
while (count < lim && i->state.next < P->end) {
if ((error = dns_rr_parse(rr, i->state.next, P)))
goto error;
rr->section = dns_rr_section(i->state.next, P);
rr++;
count++;
i->state.count++;
i->state.next = dns_rr_i_skip(i->state.next, i, P);
} /* while() */
break;
} /* switch() */
return count;
error:
if (error_)
*error_ = error;
return count;
} /* dns_rr_grep() */
size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
union dns_any any;
size_t n;
int error;
if (rr->section == DNS_S_QD)
dns_b_putc(&dst, ';');
if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error)))
goto error;
dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1));
if (rr->section != DNS_S_QD) {
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, rr->ttl, 0);
}
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strclass(rr->class));
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strtype(rr->type));
if (rr->section == DNS_S_QD)
goto epilog;
dns_b_putc(&dst, ' ');
if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P)))
goto error;
n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type);
dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p));
epilog:
return dns_b_strllen(&dst);
error:
*_error = error;
return 0;
} /* dns_rr_print() */
int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) {
unsigned long addr;
if (rr->rd.len != 4)
return DNS_EILLEGAL;
addr = ((0xffU & P->data[rr->rd.p + 0]) << 24)
| ((0xffU & P->data[rr->rd.p + 1]) << 16)
| ((0xffU & P->data[rr->rd.p + 2]) << 8)
| ((0xffU & P->data[rr->rd.p + 3]) << 0);
a->addr.s_addr = htonl(addr);
return 0;
} /* dns_a_parse() */
int dns_a_push(struct dns_packet *P, struct dns_a *a) {
unsigned long addr;
if (P->size - P->end < 6)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x04;
addr = ntohl(a->addr.s_addr);
P->data[P->end++] = 0xffU & (addr >> 24);
P->data[P->end++] = 0xffU & (addr >> 16);
P->data[P->end++] = 0xffU & (addr >> 8);
P->data[P->end++] = 0xffU & (addr >> 0);
return 0;
} /* dns_a_push() */
size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned long octets = ntohl(a->addr.s_addr);
unsigned i;
for (i = 0; i < 4; i++) {
dns_b_fmtju(&dst, 0xff & octets, 0);
dns_b_putc(&dst, '.');
octets >>= 8;
}
dns_b_puts(&dst, "in-addr.arpa.");
return dns_b_strllen(&dst);
} /* dns_a_arpa() */
int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) {
if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr))
return -1;
if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr))
return 1;
return 0;
} /* dns_a_cmp() */
size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) {
char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0";
dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_a_print() */
int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) {
if (rr->rd.len != sizeof aaaa->addr.s6_addr)
return DNS_EILLEGAL;
memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr);
return 0;
} /* dns_aaaa_parse() */
int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) {
if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x10;
memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr);
P->end += sizeof aaaa->addr.s6_addr;
return 0;
} /* dns_aaaa_push() */
int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) {
unsigned i;
int cmp;
for (i = 0; i < lengthof(a->addr.s6_addr); i++) {
if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i])))
return cmp;
}
return 0;
} /* dns_aaaa_cmp() */
size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned nyble;
int i, j;
for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) {
nyble = aaaa->addr.s6_addr[i];
for (j = 0; j < 2; j++) {
dns_b_putc(&dst, hex[0x0f & nyble]);
dns_b_putc(&dst, '.');
nyble >>= 4;
}
}
dns_b_puts(&dst, "ip6.arpa.");
return dns_b_strllen(&dst);
} /* dns_aaaa_arpa() */
size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) {
char addr[INET6_ADDRSTRLEN + 1] = "::";
dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_aaaa_print() */
int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (rr->rd.len < 3)
return DNS_EILLEGAL;
mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8))
| (0x00ff & (P->data[rr->rd.p + 1] << 0));
if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error)))
return error;
else if (len >= sizeof mx->host)
return DNS_EILLEGAL;
return 0;
} /* dns_mx_parse() */
int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) {
size_t end, len;
int error;
if (P->size - P->end < 5)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
P->data[P->end++] = 0xff & (mx->preference >> 8);
P->data[P->end++] = 0xff & (mx->preference >> 0);
if ((error = dns_d_push(P, mx->host, strlen(mx->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_mx_push() */
int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) {
int cmp;
if ((cmp = a->preference - b->preference))
return cmp;
return strcasecmp(a->host, b->host);
} /* dns_mx_cmp() */
size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, mx->preference, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, mx->host);
return dns_b_strllen(&dst);
} /* dns_mx_print() */
size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) {
return dns_strlcpy(dst, mx->host, lim);
} /* dns_mx_cname() */
int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error)))
return error;
else if (len >= sizeof ns->host)
return DNS_EILLEGAL;
return 0;
} /* dns_ns_parse() */
int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) {
size_t end, len;
int error;
if (P->size - P->end < 3)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
if ((error = dns_d_push(P, ns->host, strlen(ns->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_ns_push() */
int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) {
return strcasecmp(a->host, b->host);
} /* dns_ns_cmp() */
size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_print() */
size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_cname() */
int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)cname, rr, P);
} /* dns_cname_parse() */
int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) {
return dns_ns_push(P, (struct dns_ns *)cname);
} /* dns_cname_push() */
int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) {
return strcasecmp(a->host, b->host);
} /* dns_cname_cmp() */
size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) {
return dns_ns_print(dst, lim, (struct dns_ns *)cname);
} /* dns_cname_print() */
size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) {
return dns_strlcpy(dst, cname->host, lim);
} /* dns_cname_cname() */
int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) {
struct { void *dst; size_t lim; } dn[] =
{ { soa->mname, sizeof soa->mname },
{ soa->rname, sizeof soa->rname } };
unsigned *ts[] =
{ &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum };
unsigned short rp;
unsigned i, j, n;
int error;
/* MNAME / RNAME */
if ((rp = rr->rd.p) >= P->end)
return DNS_EILLEGAL;
for (i = 0; i < lengthof(dn); i++) {
if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error)))
return error;
else if (n >= dn[i].lim)
return DNS_EILLEGAL;
if ((rp = dns_d_skip(rp, P)) >= P->end)
return DNS_EILLEGAL;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
for (j = 0; j < 4; j++, rp++) {
if (rp >= P->end)
return DNS_EILLEGAL;
*ts[i] <<= 8;
*ts[i] |= (0xff & P->data[rp]);
}
}
return 0;
} /* dns_soa_parse() */
int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) {
void *dn[] = { soa->mname, soa->rname };
unsigned ts[] = { (0xffffffff & soa->serial),
(0x7fffffff & soa->refresh),
(0x7fffffff & soa->retry),
(0x7fffffff & soa->expire),
(0xffffffff & soa->minimum) };
unsigned i, j;
size_t end, len;
int error;
end = P->end;
if ((P->end += 2) >= P->size)
goto toolong;
/* MNAME / RNAME */
for (i = 0; i < lengthof(dn); i++) {
if ((error = dns_d_push(P, dn[i], strlen(dn[i]))))
goto error;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
if ((P->end += 4) >= P->size)
goto toolong;
for (j = 1; j <= 4; j++) {
P->data[P->end - j] = (0xff & ts[i]);
ts[i] >>= 8;
}
}
len = P->end - end - 2;
P->data[end + 0] = (0xff & (len >> 8));
P->data[end + 1] = (0xff & (len >> 0));
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_soa_push() */
int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) {
int cmp;
if ((cmp = strcasecmp(a->mname, b->mname)))
return cmp;
if ((cmp = strcasecmp(a->rname, b->rname)))
return cmp;
if (a->serial > b->serial)
return -1;
else if (a->serial < b->serial)
return 1;
if (a->refresh > b->refresh)
return -1;
else if (a->refresh < b->refresh)
return 1;
if (a->retry > b->retry)
return -1;
else if (a->retry < b->retry)
return 1;
if (a->expire > b->expire)
return -1;
else if (a->expire < b->expire)
return 1;
if (a->minimum > b->minimum)
return -1;
else if (a->minimum < b->minimum)
return 1;
return 0;
} /* dns_soa_cmp() */
size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_puts(&dst, soa->mname);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, soa->rname);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->serial, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->refresh, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->retry, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->expire, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->minimum, 0);
return dns_b_strllen(&dst);
} /* dns_soa_print() */
int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) {
unsigned short rp;
unsigned i;
size_t n;
int error;
memset(srv, '\0', sizeof *srv);
rp = rr->rd.p;
if (rr->rd.len < 7)
return DNS_EILLEGAL;
for (i = 0; i < 2; i++, rp++) {
srv->priority <<= 8;
srv->priority |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->weight <<= 8;
srv->weight |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->port <<= 8;
srv->port |= (0xff & P->data[rp]);
}
if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error)))
return error;
else if (n >= sizeof srv->target)
return DNS_EILLEGAL;
return 0;
} /* dns_srv_parse() */
int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) {
size_t end, len;
int error;
end = P->end;
if (P->size - P->end < 2)
goto toolong;
P->end += 2;
if (P->size - P->end < 6)
goto toolong;
P->data[P->end++] = 0xff & (srv->priority >> 8);
P->data[P->end++] = 0xff & (srv->priority >> 0);
P->data[P->end++] = 0xff & (srv->weight >> 8);
P->data[P->end++] = 0xff & (srv->weight >> 0);
P->data[P->end++] = 0xff & (srv->port >> 8);
P->data[P->end++] = 0xff & (srv->port >> 0);
if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error)))
goto error;
else if (P->size - P->end < len)
goto toolong;
P->end += len;
if (P->end > 65535)
goto toolong;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_srv_push() */
int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) {
int cmp;
if ((cmp = a->priority - b->priority))
return cmp;
/*
* FIXME: We need some sort of random seed to implement the dynamic
* weighting required by RFC 2782.
*/
if ((cmp = a->weight - b->weight))
return cmp;
if ((cmp = a->port - b->port))
return cmp;
return strcasecmp(a->target, b->target);
} /* dns_srv_cmp() */
size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, srv->priority, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->weight, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->port, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, srv->target);
return dns_b_strllen(&dst);
} /* dns_srv_print() */
size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) {
return dns_strlcpy(dst, srv->target, lim);
} /* dns_srv_cname() */
unsigned int dns_opt_ttl(const struct dns_opt *opt) {
unsigned int ttl = 0;
ttl |= (0xffU & opt->rcode) << 24;
ttl |= (0xffU & opt->version) << 16;
ttl |= (0xffffU & opt->flags) << 0;
return ttl;
} /* dns_opt_ttl() */
unsigned short dns_opt_class(const struct dns_opt *opt) {
return opt->maxudp;
} /* dns_opt_class() */
struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) {
assert(size >= offsetof(struct dns_opt, data));
opt->size = size - offsetof(struct dns_opt, data);
opt->len = 0;
opt->rcode = 0;
opt->version = 0;
opt->maxudp = 0;
return opt;
} /* dns_opt_init() */
static union dns_any *dns_opt_initany(union dns_any *any, size_t size) {
return dns_opt_init(&any->opt, size), any;
} /* dns_opt_initany() */
int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len);
struct dns_buf dst = DNS_B_INTO(opt->data, opt->size);
int error;
opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode);
opt->version = 0xff & (rr->ttl >> 16);
opt->flags = 0xffff & rr->ttl;
opt->maxudp = 0xffff & rr->class;
while (src.p < src.pe) {
int code, len;
if (-1 == (code = dns_b_get16(&src, -1)))
return src.error;
if (-1 == (len = dns_b_get16(&src, -1)))
return src.error;
switch (code) {
default:
dns_b_put16(&dst, code);
dns_b_put16(&dst, len);
if ((error = dns_b_move(&dst, &src, len)))
return error;
break;
}
}
return 0;
} /* dns_opt_parse() */
int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) {
const struct dns_buf src = DNS_B_FROM(opt->data, opt->len);
struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end));
int error;
/* rdata length (see below) */
if ((error = dns_b_put16(&dst, 0)))
goto error;
/* ... push known options here */
/* push opaque option data */
if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p))))
goto error;
/* rdata length */
if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0)))
goto error;
#if !DNS_DEBUG_OPT_FORMERR
P->end += dns_b_tell(&dst);
#endif
return 0;
error:
return error;
} /* dns_opt_push() */
int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) {
(void)a;
(void)b;
return -1;
} /* dns_opt_cmp() */
size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t p;
dns_b_putc(&dst, '"');
for (p = 0; p < opt->len; p++) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, opt->data[p], 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_opt_print() */
int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)ptr, rr, P);
} /* dns_ptr_parse() */
int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) {
return dns_ns_push(P, (struct dns_ns *)ptr);
} /* dns_ptr_push() */
size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) {
switch (af) {
case AF_INET6:
return dns_aaaa_arpa(dst, lim, addr);
case AF_INET:
return dns_a_arpa(dst, lim, addr);
default: {
struct dns_a a;
a.addr.s_addr = INADDR_NONE;
return dns_a_arpa(dst, lim, &a);
}
}
} /* dns_ptr_qname() */
int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) {
return strcasecmp(a->host, b->host);
} /* dns_ptr_cmp() */
size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_ns_print(dst, lim, (struct dns_ns *)ptr);
} /* dns_ptr_print() */
size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_strlcpy(dst, ptr->host, lim);
} /* dns_ptr_cname() */
int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) {
unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len;
if (pe - p < 2)
return DNS_EILLEGAL;
fp->algo = P->data[p++];
fp->type = P->data[p++];
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_EILLEGAL;
memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1);
break;
default:
break;
} /* switch() */
return 0;
} /* dns_sshfp_parse() */
int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) {
unsigned p = P->end, pe = P->size, n;
if (pe - p < 4)
return DNS_ENOBUFS;
p += 2;
P->data[p++] = 0xff & fp->algo;
P->data[p++] = 0xff & fp->type;
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_ENOBUFS;
memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1);
p += sizeof fp->digest.sha1;
break;
default:
return DNS_EILLEGAL;
} /* switch() */
n = p - P->end - 2;
P->data[P->end++] = 0xff & (n >> 8);
P->data[P->end++] = 0xff & (n >> 0);
P->end = p;
return 0;
} /* dns_sshfp_push() */
int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) {
int cmp;
if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type))
return cmp;
switch (a->type) {
case DNS_SSHFP_SHA1:
return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1);
default:
return 0;
} /* switch() */
/* NOT REACHED */
} /* dns_sshfp_cmp() */
size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t i;
dns_b_fmtju(&dst, fp->algo, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, fp->type, 0);
dns_b_putc(&dst, ' ');
switch (fp->type) {
case DNS_SSHFP_SHA1:
for (i = 0; i < sizeof fp->digest.sha1; i++) {
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]);
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]);
}
break;
default:
dns_b_putc(&dst, '0');
break;
} /* switch() */
return dns_b_strllen(&dst);
} /* dns_sshfp_print() */
struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) {
assert(size > offsetof(struct dns_txt, data));
txt->size = size - offsetof(struct dns_txt, data);
txt->len = 0;
return txt;
} /* dns_txt_init() */
static union dns_any *dns_txt_initany(union dns_any *any, size_t size) {
/* NB: union dns_any is already initialized as struct dns_txt */
(void)size;
return any;
} /* dns_txt_initany() */
int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = txt->data;
dst.p = 0;
dst.end = txt->size;
src.b = P->data;
src.p = rr->rd.p;
src.end = src.p + rr->rd.len;
while (src.p < src.end) {
n = 0xff & P->data[src.p++];
if (src.end - src.p < n || dst.end - dst.p < n)
return DNS_EILLEGAL;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
txt->len = dst.p;
return 0;
} /* dns_txt_parse() */
int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = P->data;
dst.p = P->end;
dst.end = P->size;
src.b = txt->data;
src.p = 0;
src.end = txt->len;
if (dst.end - dst.p < 2)
return DNS_ENOBUFS;
n = txt->len + ((txt->len + 254) / 255);
dst.b[dst.p++] = 0xff & (n >> 8);
dst.b[dst.p++] = 0xff & (n >> 0);
while (src.p < src.end) {
n = DNS_PP_MIN(255, src.end - src.p);
if (dst.p >= dst.end)
return DNS_ENOBUFS;
dst.b[dst.p++] = n;
if (dst.end - dst.p < n)
return DNS_ENOBUFS;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
P->end = dst.p;
return 0;
} /* dns_txt_push() */
int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) {
(void)a;
(void)b;
return -1;
} /* dns_txt_cmp() */
size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) {
struct dns_buf src = DNS_B_FROM(txt->data, txt->len);
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned i;
if (src.p < src.pe) {
do {
dns_b_putc(&dst, '"');
for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) {
if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p, 3);
} else {
dns_b_putc(&dst, *src.p);
}
}
dns_b_putc(&dst, '"');
dns_b_putc(&dst, ' ');
} while (src.p < src.pe);
dns_b_popc(&dst);
} else {
dns_b_putc(&dst, '"');
dns_b_putc(&dst, '"');
}
return dns_b_strllen(&dst);
} /* dns_txt_print() */
/* Some of the function pointers of DNS_RRTYPES are initialized with
* slighlly different functions, thus we can't use prototypes. */
DNS_PRAGMA_PUSH
#if __clang__
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#endif
static const struct dns_rrtype {
enum dns_type type;
const char *name;
union dns_any *(*init)(union dns_any *, size_t);
int (*parse)();
int (*push)();
int (*cmp)();
size_t (*print)();
size_t (*cname)();
} dns_rrtypes[] = {
{ DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, },
{ DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, },
{ DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, },
{ DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, },
{ DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, },
{ DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, },
{ DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, },
{ DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, },
{ DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, },
{ DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, },
{ DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, },
{ DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, },
{ DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, },
}; /* dns_rrtypes[] */
DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/
static const struct dns_rrtype *dns_rrtype(enum dns_type type) {
const struct dns_rrtype *t;
for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) {
if (t->type == type && t->parse) {
return t;
}
}
return NULL;
} /* dns_rrtype() */
union dns_any *dns_any_init(union dns_any *any, size_t size) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return (union dns_any *)dns_txt_init(&any->rdata, size);
} /* dns_any_init() */
static size_t dns_any_sizeof(union dns_any *any) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return offsetof(struct dns_txt, data) + any->rdata.size;
} /* dns_any_sizeof() */
static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) {
return (t->init)? t->init(any, dns_any_sizeof(any)) : any;
} /* dns_any_reinit() */
int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(rr->type)))
return t->parse(dns_any_reinit(any, t), rr, P);
if (rr->rd.len > any->rdata.size)
return DNS_EILLEGAL;
memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len);
any->rdata.len = rr->rd.len;
return 0;
} /* dns_any_parse() */
int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)))
return t->push(P, any);
if (P->size - P->end < any->rdata.len + 2)
return DNS_ENOBUFS;
P->data[P->end++] = 0xff & (any->rdata.len >> 8);
P->data[P->end++] = 0xff & (any->rdata.len >> 0);
memcpy(&P->data[P->end], any->rdata.data, any->rdata.len);
P->end += any->rdata.len;
return 0;
} /* dns_any_push() */
int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) {
const struct dns_rrtype *t;
int cmp;
if ((cmp = x - y))
return cmp;
if ((t = dns_rrtype(x)))
return t->cmp(a, b);
return -1;
} /* dns_any_cmp() */
size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
struct dns_buf src, dst;
if ((t = dns_rrtype(type)))
return t->print(_dst, lim, any);
dns_b_from(&src, any->rdata.data, any->rdata.len);
dns_b_into(&dst, _dst, lim);
dns_b_putc(&dst, '"');
while (src.p < src.pe) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p++, 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_any_print() */
size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)) && t->cname)
return t->cname(dst, lim, any);
return 0;
} /* dns_any_cname() */
/*
* E V E N T T R A C I N G R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <float.h> /* DBL_MANT_DIG */
#include <inttypes.h> /* PRIu64 */
/* for default trace ID generation try to fit in lua_Number, usually double */
#define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */
#define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)))
#define DNS_TRACE_ID_PRI PRIu64
static inline dns_trace_id_t dns_trace_mkid(void) {
dns_trace_id_t id = 0;
unsigned r; /* return type of dns_random() */
const size_t id_bit = sizeof id * CHAR_BIT;
const size_t r_bit = sizeof r * CHAR_BIT;
for (size_t n = 0; n < id_bit; n += r_bit) {
r = dns_random();
id <<= r_bit;
id |= r;
}
return DNS_TRACE_ID_MASK & id;
}
struct dns_trace {
dns_atomic_t refcount;
FILE *fp;
dns_trace_id_t id;
struct {
struct dns_trace_cname {
char host[DNS_D_MAXNAME + 1];
struct sockaddr_storage addr;
} base[4];
size_t p;
} cnames;
};
static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) {
socklen_t n = sizeof *ss;
if (0 != f(fd, (struct sockaddr *)ss, &n))
goto unspec;
if (n > sizeof *ss)
goto unspec;
return;
unspec:
memset(ss, '\0', sizeof *ss);
ss->ss_family = AF_UNSPEC;
}
static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) {
dns_te_initname(local, fd, &getsockname);
dns_te_initname(remote, fd, &getpeername);
}
static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) {
/* NB: silence valgrind */
memset(te, '\0', offsetof(struct dns_trace_event, data));
te->type = type;
return te;
}
int dns_trace_abi(void) {
return DNS_TRACE_ABI;
}
struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) {
static const struct dns_trace trace_initializer = { .refcount = 1 };
struct dns_trace *trace;
if (!(trace = malloc(sizeof *trace)))
goto syerr;
*trace = trace_initializer;
if (fp) {
trace->fp = fp;
}
trace->id = dns_trace_mkid();
return trace;
syerr:
*error = dns_syerr();
dns_trace_close(trace);
return NULL;
} /* dns_trace_open() */
void dns_trace_close(struct dns_trace *trace) {
if (!trace || 1 != dns_trace_release(trace))
return;
if (trace->fp)
fclose(trace->fp);
free(trace);
} /* dns_trace_close() */
dns_refcount_t dns_trace_acquire(struct dns_trace *trace) {
return dns_atomic_fetch_add(&trace->refcount);
} /* dns_trace_acquire() */
static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) {
return (trace)? dns_trace_acquire(trace), trace : NULL;
} /* dns_trace_acquire_p() */
dns_refcount_t dns_trace_release(struct dns_trace *trace) {
return dns_atomic_fetch_sub(&trace->refcount);
} /* dns_trace_release() */
dns_trace_id_t dns_trace_id(struct dns_trace *trace) {
return trace->id;
} /* dns_trace_id() */
dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) {
trace->id = (id)? id : dns_trace_mkid();
return trace->id;
} /* dns_trace_setid() */
struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) {
return dns_trace_fget(tp, trace->fp, error);
} /* dns_trace_get() */
dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_fput(te, data, datasize, trace->fp);
} /* dns_trace_put() */
struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) {
struct timeval tv;
te->id = trace->id;
gettimeofday(&tv, NULL);
dns_tv2ts(&te->ts, &tv);
te->abi = DNS_TRACE_ABI;
return te;
} /* dns_trace_tag() */
static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize);
} /* dns_trace_tag_and_put() */
struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) {
const size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp, *te;
size_t n;
errno = 0;
if (!(n = fread(&tmp, 1, headsize, fp)))
goto none;
if (n < offsetof(struct dns_trace_event, data))
goto some;
if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) {
*error = errno;
return NULL;
}
*tp = te;
memcpy(te, &tmp, offsetof(struct dns_trace_event, data));
if (dns_te_datasize(te)) {
errno = 0;
if (!(n = fread(te->data, 1, dns_te_datasize(te), fp)))
goto none;
if (n < dns_te_datasize(te))
goto some;
}
return te;
none:
*error = (ferror(fp))? errno : 0;
return NULL;
some:
*error = 0;
return NULL;
}
dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) {
size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp;
memcpy(&tmp, te, headsize);
tmp.size = headsize + datasize;
/* NB: ignore seek error as fp might not point to a regular file */
(void)fseek(fp, 0, SEEK_END);
if (fwrite(&tmp, 1, headsize, fp) < headsize)
return errno;
if (data)
if (fwrite(data, 1, datasize, fp) < datasize)
return errno;
if (fflush(fp))
return errno;
return 0;
}
static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) {
struct dns_trace_cname *cname;
if (!trace || !trace->fp)
return;
cname = &trace->cnames.base[trace->cnames.p];
dns_strlcpy(cname->host, host, sizeof cname->host);
memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr));
trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base);
}
static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) {
if (!trace || !trace->fp)
return NULL;
/* NB: start search from the write cursor to */
for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) {
if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr))
return cname->host;
}
return NULL;
}
static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_SUBMIT);
dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname);
te.res_submit.qtype = qtype;
te.res_submit.qclass = qclass;
te.res_submit.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.res_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) {
struct dns_trace_event te;
const char *cname;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_SUBMIT);
memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr));
if ((cname = dns_trace_cname(trace, haddr)))
dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname);
te.so_submit.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_VERIFY);
te.so_verify.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.so_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_CONNECT);
dns_te_initname(&te.sys_connect.src, fd, &getsockname);
memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst));
te.sys_connect.socktype = socktype;
te.sys_connect.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_SEND);
dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd);
te.sys_send.socktype = socktype;
te.sys_send.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_RECV);
dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd);
te.sys_recv.socktype = socktype;
te.sys_recv.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_packet *packet = NULL;
char *line = NULL, *p;
size_t size = 1, skip = 0;
struct dns_rr_i records;
struct dns_p_lines_i lines;
size_t len, count;
int error;
if (!(packet = dns_p_make(datasize, &error)))
goto error;
memcpy(packet->data, data, datasize);
packet->end = datasize;
(void)dns_p_study(packet);
resize:
if (!(p = dns_reallocarray(line, size, 2, &error)))
goto error;
line = p;
size *= 2;
memset(&records, 0, sizeof records);
memset(&lines, 0, sizeof lines);
count = 0;
while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) {
if (!(len < size)) {
skip = count;
goto resize;
} else if (skip <= count) {
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
count++;
}
if (error)
goto error;
error = 0;
error:
free(line);
dns_p_free(packet);
return error;
}
static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
size_t len;
while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) {
if (len >= sizeof line)
return EOVERFLOW; /* shouldn't be possible */
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
return 0;
}
static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) {
const void *addr;
const char *path;
socklen_t len;
int error;
if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) {
char ip[INET6_ADDRSTRLEN + 1];
if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip)))
return error;
fprintf(fp, "%s%s\n", prefix, ip);
} else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) {
fprintf(fp, "%sunix:%.*s", prefix, (int)len, path);
} else {
return EINVAL;
}
return 0;
}
static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) {
char time_s[48], elapsed_s[48];
dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0));
dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed);
fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id);
fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s);
fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI);
return 0;
}
static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) {
fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none");
return 0;
}
dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) {
struct dns_trace_event *te = NULL;
struct {
dns_trace_id_t id;
dns_microseconds_t begin, elapsed;
} state = { 0 };
int error;
if (!trace || !trace->fp)
return EINVAL;
if (0 != fseek(trace->fp, 0, SEEK_SET))
goto syerr;
while (dns_trace_fget(&te, trace->fp, &error)) {
size_t datasize = dns_te_datasize(te);
const unsigned char *data = (datasize)? te->data : NULL;
if (state.id != te->id) {
state.id = te->id;
state.begin = dns_ts2us(&te->ts, 0);
}
dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin);
switch(te->type) {
case DNS_TE_RES_SUBMIT:
fprintf(fp, "dns_res_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " qname: %s\n", te->res_submit.qname);
fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype));
fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass));
dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp);
break;
case DNS_TE_RES_FETCH:
fprintf(fp, "dns_res_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_SUBMIT:
fprintf(fp, "dns_so_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " hname: %s\n", te->so_submit.hname);
dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp);
dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_VERIFY:
fprintf(fp, "dns_so_verify:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_FETCH:
fprintf(fp, "dns_so_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SYS_CONNECT: {
int socktype = te->sys_connect.socktype;
fprintf(fp, "dns_sys_connect:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp);
break;
}
case DNS_TE_SYS_SEND: {
int socktype = te->sys_send.socktype;
fprintf(fp, "dns_sys_send:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
case DNS_TE_SYS_RECV: {
int socktype = te->sys_recv.socktype;
fprintf(fp, "dns_sys_recv:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
default:
fprintf(fp, "unknown(0x%.2x):\n", te->type);
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
}
goto epilog;
syerr:
error = errno;
error:
(void)0;
epilog:
free(te);
return error;
}
/*
* H O S T S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hosts {
struct dns_hosts_entry {
char host[DNS_D_MAXNAME + 1];
char arpa[73 + 1];
int af;
union {
struct in_addr a4;
struct in6_addr a6;
} addr;
_Bool alias;
struct dns_hosts_entry *next;
} *head, **tail;
dns_atomic_t refcount;
}; /* struct dns_hosts */
struct dns_hosts *dns_hosts_open(int *error) {
static const struct dns_hosts hosts_initializer = { .refcount = 1 };
struct dns_hosts *hosts;
if (!(hosts = malloc(sizeof *hosts)))
goto syerr;
*hosts = hosts_initializer;
hosts->tail = &hosts->head;
return hosts;
syerr:
*error = dns_syerr();
free(hosts);
return 0;
} /* dns_hosts_open() */
void dns_hosts_close(struct dns_hosts *hosts) {
struct dns_hosts_entry *ent, *xnt;
if (!hosts || 1 != dns_hosts_release(hosts))
return;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
free(ent);
}
free(hosts);
return;
} /* dns_hosts_close() */
dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) {
return dns_atomic_fetch_add(&hosts->refcount);
} /* dns_hosts_acquire() */
dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) {
return dns_atomic_fetch_sub(&hosts->refcount);
} /* dns_hosts_release() */
struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) {
if (hosts)
dns_hosts_release(hosts);
return hosts;
} /* dns_hosts_mortal() */
struct dns_hosts *dns_hosts_local(int *error_) {
struct dns_hosts *hosts;
int error;
if (!(hosts = dns_hosts_open(&error)))
goto error;
if ((error = dns_hosts_loadpath(hosts, "/etc/hosts")))
goto error;
return hosts;
error:
*error_ = error;
dns_hosts_close(hosts);
return 0;
} /* dns_hosts_local() */
#define dns_hosts_issep(ch) (dns_isspace(ch))
#define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry ent;
char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1];
unsigned wp, wc, skip;
int ch, error;
rewind(fp);
do {
memset(&ent, '\0', sizeof ent);
wc = 0;
skip = 0;
do {
memset(word, '\0', sizeof word);
wp = 0;
while (EOF != (ch = fgetc(fp)) && ch != '\n') {
skip |= !!dns_hosts_iscom(ch);
if (skip)
continue;
if (dns_hosts_issep(ch))
break;
if (wp < sizeof word - 1)
word[wp] = ch;
wp++;
}
if (!wp)
continue;
wc++;
switch (wc) {
case 0:
break;
case 1:
ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET;
skip = (1 != dns_inet_pton(ent.af, word, &ent.addr));
break;
default:
if (!wp)
break;
dns_d_anchor(ent.host, sizeof ent.host, word, wp);
if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2))))
return error;
break;
} /* switch() */
} while (ch != EOF && ch != '\n');
} while (ch != EOF);
return 0;
} /* dns_hosts_loadfile() */
int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_hosts_loadfile(hosts, fp);
fclose(fp);
return error;
} /* dns_hosts_loadpath() */
int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry *ent, *xnt;
char addr[INET6_ADDRSTRLEN + 1];
unsigned i;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr);
fputs(addr, fp);
for (i = strlen(addr); i < INET_ADDRSTRLEN; i++)
fputc(' ', fp);
fputc(' ', fp);
fputs(ent->host, fp);
fputc('\n', fp);
}
return 0;
} /* dns_hosts_dump() */
int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) {
struct dns_hosts_entry *ent;
int error;
if (!(ent = malloc(sizeof *ent)))
goto syerr;
dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host));
switch ((ent->af = af)) {
case AF_INET6:
memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6);
dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
case AF_INET:
memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4);
dns_a_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
default:
error = EINVAL;
goto error;
} /* switch() */
ent->alias = alias;
ent->next = 0;
*hosts->tail = ent;
hosts->tail = &ent->next;
return 0;
syerr:
error = dns_syerr();
error:
free(ent);
return error;
} /* dns_hosts_insert() */
struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
struct dns_packet *A = 0;
struct dns_rr rr;
struct dns_hosts_entry *ent;
int error, af;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error)))
goto error;
else if (qlen >= sizeof qname)
goto toolong;
if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0)))
goto error;
switch (rr.type) {
case DNS_T_PTR:
for (ent = hosts->head; ent; ent = ent->next) {
if (ent->alias || 0 != strcasecmp(qname, ent->arpa))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host)))
goto error;
}
break;
case DNS_T_AAAA:
af = AF_INET6;
goto loop;
case DNS_T_A:
af = AF_INET;
loop: for (ent = hosts->head; ent; ent = ent->next) {
if (ent->af != af || 0 != strcasecmp(qname, ent->host))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr)))
goto error;
}
break;
default:
break;
} /* switch() */
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
dns_p_free(A);
return 0;
} /* dns_hosts_query() */
/*
* R E S O L V . C O N F R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_resolv_conf *dns_resconf_open(int *error) {
static const struct dns_resolv_conf resconf_initializer = {
.lookup = "bf",
.family = { AF_INET, AF_INET6 },
.options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, },
.iface = { .ss_family = AF_INET },
};
struct dns_resolv_conf *resconf;
struct sockaddr_in *sin;
if (!(resconf = malloc(sizeof *resconf)))
goto syerr;
*resconf = resconf_initializer;
sin = (struct sockaddr_in *)&resconf->nameserver[0];
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(53);
#if defined(SA_LEN)
sin->sin_len = sizeof *sin;
#endif
if (0 != gethostname(resconf->search[0], sizeof resconf->search[0]))
goto syerr;
/*
* If gethostname() returned a string without any label
* separator, then search[0][0] should be NUL.
*/
if (strchr (resconf->search[0], '.')) {
dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
} else {
memset (resconf->search[0], 0, sizeof resconf->search[0]);
}
dns_resconf_acquire(resconf);
return resconf;
syerr:
*error = dns_syerr();
free(resconf);
return 0;
} /* dns_resconf_open() */
void dns_resconf_close(struct dns_resolv_conf *resconf) {
if (!resconf || 1 != dns_resconf_release(resconf))
return /* void */;
free(resconf);
} /* dns_resconf_close() */
dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_add(&resconf->_.refcount);
} /* dns_resconf_acquire() */
dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_sub(&resconf->_.refcount);
} /* dns_resconf_release() */
struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) {
if (resconf)
dns_resconf_release(resconf);
return resconf;
} /* dns_resconf_mortal() */
struct dns_resolv_conf *dns_resconf_local(int *error_) {
struct dns_resolv_conf *resconf;
int error;
if (!(resconf = dns_resconf_open(&error)))
goto error;
if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) {
/*
* NOTE: Both the glibc and BIND9 resolvers ignore a missing
* /etc/resolv.conf, defaulting to a nameserver of
* 127.0.0.1. See also dns_hints_insert_resconf, and the
* default initialization of nameserver[0] in
* dns_resconf_open.
*/
if (error != ENOENT)
goto error;
}
if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) {
if (error != ENOENT)
goto error;
}
return resconf;
error:
*error_ = error;
dns_resconf_close(resconf);
return 0;
} /* dns_resconf_local() */
struct dns_resolv_conf *dns_resconf_root(int *error) {
struct dns_resolv_conf *resconf;
if ((resconf = dns_resconf_local(error)))
resconf->options.recurse = 1;
return resconf;
} /* dns_resconf_root() */
static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) {
return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout);
} /* dns_resconf_timeout() */
enum dns_resconf_keyword {
DNS_RESCONF_NAMESERVER,
DNS_RESCONF_DOMAIN,
DNS_RESCONF_SEARCH,
DNS_RESCONF_LOOKUP,
DNS_RESCONF_FILE,
DNS_RESCONF_BIND,
DNS_RESCONF_CACHE,
DNS_RESCONF_FAMILY,
DNS_RESCONF_INET4,
DNS_RESCONF_INET6,
DNS_RESCONF_OPTIONS,
DNS_RESCONF_EDNS0,
DNS_RESCONF_NDOTS,
DNS_RESCONF_TIMEOUT,
DNS_RESCONF_ATTEMPTS,
DNS_RESCONF_ROTATE,
DNS_RESCONF_RECURSE,
DNS_RESCONF_SMART,
DNS_RESCONF_TCP,
DNS_RESCONF_TCPx,
DNS_RESCONF_INTERFACE,
DNS_RESCONF_ZERO,
DNS_RESCONF_ONE,
DNS_RESCONF_ENABLE,
DNS_RESCONF_ONLY,
DNS_RESCONF_DISABLE,
}; /* enum dns_resconf_keyword */
static enum dns_resconf_keyword dns_resconf_keyword(const char *word) {
static const char *words[] = {
[DNS_RESCONF_NAMESERVER] = "nameserver",
[DNS_RESCONF_DOMAIN] = "domain",
[DNS_RESCONF_SEARCH] = "search",
[DNS_RESCONF_LOOKUP] = "lookup",
[DNS_RESCONF_FILE] = "file",
[DNS_RESCONF_BIND] = "bind",
[DNS_RESCONF_CACHE] = "cache",
[DNS_RESCONF_FAMILY] = "family",
[DNS_RESCONF_INET4] = "inet4",
[DNS_RESCONF_INET6] = "inet6",
[DNS_RESCONF_OPTIONS] = "options",
[DNS_RESCONF_EDNS0] = "edns0",
[DNS_RESCONF_ROTATE] = "rotate",
[DNS_RESCONF_RECURSE] = "recurse",
[DNS_RESCONF_SMART] = "smart",
[DNS_RESCONF_TCP] = "tcp",
[DNS_RESCONF_INTERFACE] = "interface",
[DNS_RESCONF_ZERO] = "0",
[DNS_RESCONF_ONE] = "1",
[DNS_RESCONF_ENABLE] = "enable",
[DNS_RESCONF_ONLY] = "only",
[DNS_RESCONF_DISABLE] = "disable",
};
unsigned i;
for (i = 0; i < lengthof(words); i++) {
if (words[i] && 0 == strcasecmp(words[i], word))
return i;
}
if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1))
return DNS_RESCONF_NDOTS;
if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1))
return DNS_RESCONF_TIMEOUT;
if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1))
return DNS_RESCONF_ATTEMPTS;
if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1))
return DNS_RESCONF_TCPx;
return -1;
} /* dns_resconf_keyword() */
/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */
int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) {
struct { char buf[128], *p; } addr = { "", addr.buf };
unsigned short port = 0;
int ch, af = AF_INET, error;
memset(ss, 0, sizeof *ss);
while ((ch = *src++)) {
switch (ch) {
case ' ':
/* FALL THROUGH */
case '\t':
break;
case '[':
break;
case ']':
while ((ch = *src++)) {
if (dns_isdigit(ch)) {
port *= 10;
port += ch - '0';
}
}
goto inet;
case ':':
af = AF_INET6;
/* FALL THROUGH */
default:
if (addr.p < endof(addr.buf) - 1)
*addr.p++ = ch;
break;
} /* switch() */
} /* while() */
inet:
if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL))))
return error;
port = (!port)? 53 : port;
*dns_sa_port(af, ss) = htons(port);
dns_sa_family(ss) = af;
return 0;
} /* dns_resconf_pton() */
#define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',')
#define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned sa_count = 0;
char words[6][DNS_D_MAXNAME + 1];
unsigned wp, wc, i, j, n;
int ch, error;
rewind(fp);
do {
memset(words, '\0', sizeof words);
wp = 0;
wc = 0;
while (EOF != (ch = getc(fp)) && ch != '\n') {
if (dns_resconf_issep(ch)) {
if (wp > 0) {
wp = 0;
if (++wc >= lengthof(words))
goto skip;
}
} else if (dns_resconf_iscom(ch)) {
skip:
do {
ch = getc(fp);
} while (ch != EOF && ch != '\n');
break;
} else if (wp < sizeof words[wc] - 1) {
words[wc][wp++] = ch;
} else {
wp = 0; /* drop word */
goto skip;
}
}
if (wp > 0)
wc++;
if (wc < 2)
continue;
switch (dns_resconf_keyword(words[0])) {
case DNS_RESCONF_NAMESERVER:
if (sa_count >= lengthof(resconf->nameserver))
continue;
if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1])))
continue;
sa_count++;
break;
case DNS_RESCONF_DOMAIN:
case DNS_RESCONF_SEARCH:
memset(resconf->search, '\0', sizeof resconf->search);
for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++)
dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i]));
break;
case DNS_RESCONF_LOOKUP:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_FILE:
resconf->lookup[j++] = 'f';
break;
case DNS_RESCONF_BIND:
resconf->lookup[j++] = 'b';
break;
case DNS_RESCONF_CACHE:
resconf->lookup[j++] = 'c';
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_FAMILY:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_INET4:
resconf->family[j++] = AF_INET;
break;
case DNS_RESCONF_INET6:
resconf->family[j++] = AF_INET6;
break;
default:
break;
}
}
break;
case DNS_RESCONF_OPTIONS:
for (i = 1; i < wc; i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_EDNS0:
resconf->options.edns0 = 1;
break;
case DNS_RESCONF_NDOTS:
for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.ndots = n;
break;
case DNS_RESCONF_TIMEOUT:
for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.timeout = n;
break;
case DNS_RESCONF_ATTEMPTS:
for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.attempts = n;
break;
case DNS_RESCONF_ROTATE:
resconf->options.rotate = 1;
break;
case DNS_RESCONF_RECURSE:
resconf->options.recurse = 1;
break;
case DNS_RESCONF_SMART:
resconf->options.smart = 1;
break;
case DNS_RESCONF_TCP:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_TCPx:
switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) {
case DNS_RESCONF_ENABLE:
resconf->options.tcp = DNS_RESCONF_TCP_ENABLE;
break;
case DNS_RESCONF_ONE:
case DNS_RESCONF_ONLY:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_ZERO:
case DNS_RESCONF_DISABLE:
resconf->options.tcp = DNS_RESCONF_TCP_DISABLE;
break;
default:
break;
} /* switch() */
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_INTERFACE:
for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) {
n *= 10;
n += words[2][i] - '0';
}
dns_resconf_setiface(resconf, words[1], n);
break;
default:
break;
} /* switch() */
} while (ch != EOF);
return 0;
} /* dns_resconf_loadfile() */
int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_resconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_resconf_loadpath() */
struct dns_anyconf {
char *token[16];
unsigned count;
char buffer[1024], *tp, *cp;
}; /* struct dns_anyconf */
static void dns_anyconf_reset(struct dns_anyconf *cf) {
cf->count = 0;
cf->tp = cf->cp = cf->buffer;
} /* dns_anyconf_reset() */
static int dns_anyconf_push(struct dns_anyconf *cf) {
if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token)))
return ENOMEM;
*cf->cp++ = '\0';
cf->token[cf->count++] = cf->tp;
cf->tp = cf->cp;
return 0;
} /* dns_anyconf_push() */
static void dns_anyconf_pop(struct dns_anyconf *cf) {
if (cf->count > 0) {
--cf->count;
cf->tp = cf->cp = cf->token[cf->count];
cf->token[cf->count] = 0;
}
} /* dns_anyconf_pop() */
static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) {
if (!(cf->cp < endof(cf->buffer)))
return ENOMEM;
*cf->cp++ = ch;
return 0;
} /* dns_anyconf_addc() */
static _Bool dns_anyconf_match(const char *pat, int mc) {
_Bool match;
int pc;
if (*pat == '^') {
match = 0;
++pat;
} else {
match = 1;
}
while ((pc = *(const unsigned char *)pat++)) {
switch (pc) {
case '%':
if (!(pc = *(const unsigned char *)pat++))
return !match;
switch (pc) {
case 'a':
if (dns_isalpha(mc))
return match;
break;
case 'd':
if (dns_isdigit(mc))
return match;
break;
case 'w':
if (dns_isalnum(mc))
return match;
break;
case 's':
if (dns_isspace(mc))
return match;
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
} /* while() */
return !match;
} /* dns_anyconf_match() */
static int dns_anyconf_peek(FILE *fp) {
int ch;
ch = getc(fp);
ungetc(ch, fp);
return ch;
} /* dns_anyconf_peek() */
static size_t dns_anyconf_skip(const char *pat, FILE *fp) {
size_t count = 0;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
count++;
continue;
}
ungetc(ch, fp);
break;
}
return count;
} /* dns_anyconf_skip() */
static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) {
size_t len;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
if ((*error = dns_anyconf_addc(cf, ch)))
return 0;
continue;
} else {
ungetc(ch, fp);
break;
}
}
if ((len = cf->cp - cf->tp)) {
if ((*error = dns_anyconf_push(cf)))
return 0;
return len;
} else {
*error = 0;
return 0;
}
} /* dns_anyconf_scan() */
DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) {
unsigned i;
fprintf(fp, "tokens:");
for (i = 0; i < cf->count; i++) {
fprintf(fp, " %s", cf->token[i]);
}
fputc('\n', fp);
} /* dns_anyconf_dump() */
enum dns_nssconf_keyword {
DNS_NSSCONF_INVALID = 0,
DNS_NSSCONF_HOSTS = 1,
DNS_NSSCONF_SUCCESS,
DNS_NSSCONF_NOTFOUND,
DNS_NSSCONF_UNAVAIL,
DNS_NSSCONF_TRYAGAIN,
DNS_NSSCONF_CONTINUE,
DNS_NSSCONF_RETURN,
DNS_NSSCONF_FILES,
DNS_NSSCONF_DNS,
DNS_NSSCONF_MDNS,
DNS_NSSCONF_LAST,
}; /* enum dns_nssconf_keyword */
static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) {
static const char *list[] = {
[DNS_NSSCONF_HOSTS] = "hosts",
[DNS_NSSCONF_SUCCESS] = "success",
[DNS_NSSCONF_NOTFOUND] = "notfound",
[DNS_NSSCONF_UNAVAIL] = "unavail",
[DNS_NSSCONF_TRYAGAIN] = "tryagain",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
unsigned i;
for (i = 1; i < lengthof(list); i++) {
if (list[i] && 0 == strcasecmp(list[i], word))
return i;
}
return DNS_NSSCONF_INVALID;
} /* dns_nssconf_keyword() */
static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) {
static const char map[] = {
['S'] = DNS_NSSCONF_SUCCESS,
['N'] = DNS_NSSCONF_NOTFOUND,
['U'] = DNS_NSSCONF_UNAVAIL,
['T'] = DNS_NSSCONF_TRYAGAIN,
['C'] = DNS_NSSCONF_CONTINUE,
['R'] = DNS_NSSCONF_RETURN,
['f'] = DNS_NSSCONF_FILES,
['F'] = DNS_NSSCONF_FILES,
['d'] = DNS_NSSCONF_DNS,
['D'] = DNS_NSSCONF_DNS,
['b'] = DNS_NSSCONF_DNS,
['B'] = DNS_NSSCONF_DNS,
['m'] = DNS_NSSCONF_MDNS,
['M'] = DNS_NSSCONF_MDNS,
};
return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID;
} /* dns_nssconf_c2k() */
DNS_PRAGMA_PUSH
DNS_PRAGMA_QUIET
static int dns_nssconf_k2c(int k) {
static const char map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = 'S',
[DNS_NSSCONF_NOTFOUND] = 'N',
[DNS_NSSCONF_UNAVAIL] = 'U',
[DNS_NSSCONF_TRYAGAIN] = 'T',
[DNS_NSSCONF_CONTINUE] = 'C',
[DNS_NSSCONF_RETURN] = 'R',
[DNS_NSSCONF_FILES] = 'f',
[DNS_NSSCONF_DNS] = 'b',
[DNS_NSSCONF_MDNS] = 'm',
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?';
} /* dns_nssconf_k2c() */
static const char *dns_nssconf_k2s(int k) {
static const char *const map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = "SUCCESS",
[DNS_NSSCONF_NOTFOUND] = "NOTFOUND",
[DNS_NSSCONF_UNAVAIL] = "UNAVAIL",
[DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : "";
} /* dns_nssconf_k2s() */
DNS_PRAGMA_POP
int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
enum dns_nssconf_keyword source, status, action;
char lookup[sizeof resconf->lookup] = "", *lp;
struct dns_anyconf cf;
size_t i;
int error;
while (!feof(fp) && !ferror(fp)) {
dns_anyconf_reset(&cf);
dns_anyconf_skip("%s", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error))
goto nextent;
if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0]))
goto nextent;
dns_anyconf_pop(&cf);
if (!dns_anyconf_skip(": \t", fp))
goto nextent;
*(lp = lookup) = '\0';
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip(" \t", fp);
if ('[' == dns_anyconf_peek(fp)) {
dns_anyconf_skip("[! \t", fp);
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip("= \t", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_pop(&cf); /* discard status */
dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */
break;
}
dns_anyconf_skip(" \t", fp);
}
dns_anyconf_skip("] \t", fp);
}
if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */
goto nextsrc;
source = dns_nssconf_keyword(cf.token[0]);
switch (source) {
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
case DNS_NSSCONF_FILES:
*lp++ = dns_nssconf_k2c(source);
break;
default:
goto nextsrc;
}
for (i = 1; i + 1 < cf.count; i += 2) {
status = dns_nssconf_keyword(cf.token[i]);
action = dns_nssconf_keyword(cf.token[i + 1]);
switch (status) {
case DNS_NSSCONF_SUCCESS:
case DNS_NSSCONF_NOTFOUND:
case DNS_NSSCONF_UNAVAIL:
case DNS_NSSCONF_TRYAGAIN:
*lp++ = dns_nssconf_k2c(status);
break;
default:
continue;
}
switch (action) {
case DNS_NSSCONF_CONTINUE:
case DNS_NSSCONF_RETURN:
break;
default:
action = (status == DNS_NSSCONF_SUCCESS)
? DNS_NSSCONF_RETURN
: DNS_NSSCONF_CONTINUE;
break;
}
*lp++ = dns_nssconf_k2c(action);
}
nextsrc:
*lp = '\0';
dns_anyconf_reset(&cf);
}
nextent:
dns_anyconf_skip("^\n", fp);
}
if (*lookup)
strncpy(resconf->lookup, lookup, sizeof resconf->lookup);
return 0;
} /* dns_nssconf_loadfile() */
int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_nssconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_nssconf_loadpath() */
struct dns_nssconf_source {
enum dns_nssconf_keyword source, success, notfound, unavail, tryagain;
}; /* struct dns_nssconf_source */
typedef unsigned dns_nssconf_i;
static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) {
return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0;
} /* dns_nssconf_peek() */
static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) {
int source, status, action;
src->source = DNS_NSSCONF_INVALID;
src->success = DNS_NSSCONF_RETURN;
src->notfound = DNS_NSSCONF_CONTINUE;
src->unavail = DNS_NSSCONF_CONTINUE;
src->tryagain = DNS_NSSCONF_CONTINUE;
while ((source = dns_nssconf_peek(resconf, *state))) {
source = dns_nssconf_c2k(source);
++*state;
switch (source) {
case DNS_NSSCONF_FILES:
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
src->source = source;
break;
default:
continue;
}
while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) {
status = dns_nssconf_c2k(status);
action = dns_nssconf_c2k(action);
switch (action) {
case DNS_NSSCONF_RETURN:
case DNS_NSSCONF_CONTINUE:
break;
default:
goto done;
}
switch (status) {
case DNS_NSSCONF_SUCCESS:
src->success = action;
break;
case DNS_NSSCONF_NOTFOUND:
src->notfound = action;
break;
case DNS_NSSCONF_UNAVAIL:
src->unavail = action;
break;
case DNS_NSSCONF_TRYAGAIN:
src->tryagain = action;
break;
default:
goto done;
}
*state += 2;
}
break;
}
done:
return src->source != DNS_NSSCONF_INVALID;
} /* dns_nssconf_next() */
static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) {
switch (status) {
case DNS_NSSCONF_SUCCESS:
if (action == DNS_NSSCONF_RETURN)
return 0;
break;
default:
if (action == DNS_NSSCONF_CONTINUE)
return 0;
break;
}
fputc(' ', fp);
if (!*count)
fputc('[', fp);
fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action));
++*count;
return 0;
} /* dns_nssconf_dump_status() */
int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
struct dns_nssconf_source src;
dns_nssconf_i i = 0;
fputs("hosts:", fp);
while (dns_nssconf_next(&src, resconf, &i)) {
unsigned n = 0;
fprintf(fp, " %s", dns_nssconf_k2s(src.source));
dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp);
if (n)
fputc(']', fp);
}
fputc('\n', fp);
return 0;
} /* dns_nssconf_dump() */
int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) {
int af = (strchr(addr, ':'))? AF_INET6 : AF_INET;
int error;
memset(&resconf->iface, 0, sizeof (struct sockaddr_storage));
if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL))))
return error;
*dns_sa_port(af, &resconf->iface) = htons(port);
resconf->iface.ss_family = af;
return 0;
} /* dns_resconf_setiface() */
#define DNS_SM_RESTORE \
do { \
pc = 0xff & (*state >> 0); \
srchi = 0xff & (*state >> 8); \
ndots = 0xff & (*state >> 16); \
} while (0)
#define DNS_SM_SAVE \
do { \
*state = ((0xff & pc) << 0) \
| ((0xff & srchi) << 8) \
| ((0xff & ndots) << 16); \
} while (0)
size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) {
unsigned pc, srchi, ndots, len;
DNS_SM_ENTER;
/* if FQDN then return as-is and finish */
if (dns_d_isanchored(qname, qlen)) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
DNS_SM_EXIT;
}
ndots = dns_d_ndots(qname, qlen);
if (ndots >= resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) {
struct dns_buf buf = DNS_B_INTO(dst, lim);
const char *dn = resconf->search[srchi++];
dns_b_put(&buf, qname, qlen);
dns_b_putc(&buf, '.');
dns_b_puts(&buf, dn);
if (!dns_d_isanchored(dn, strlen(dn)))
dns_b_putc(&buf, '.');
len = dns_b_strllen(&buf);
DNS_SM_YIELD(len);
}
if (ndots < resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
DNS_SM_LEAVE;
return dns_strlcpy(dst, "", lim);
} /* dns_resconf_search() */
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned i;
int af;
for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
unsigned short port;
dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr);
port = ntohs(*dns_sa_port(af, &resconf->nameserver[i]));
if (port == 53)
fprintf(fp, "nameserver %s\n", addr);
else
fprintf(fp, "nameserver [%s]:%hu\n", addr, port);
}
fprintf(fp, "search");
for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++)
fprintf(fp, " %s", resconf->search[i]);
fputc('\n', fp);
fputs("; ", fp);
dns_nssconf_dump(resconf, fp);
fprintf(fp, "lookup");
for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) {
switch (resconf->lookup[i]) {
case 'b':
fprintf(fp, " bind"); break;
case 'f':
fprintf(fp, " file"); break;
case 'c':
fprintf(fp, " cache"); break;
}
}
fputc('\n', fp);
fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts);
if (resconf->options.edns0)
fprintf(fp, " edns0");
if (resconf->options.rotate)
fprintf(fp, " rotate");
if (resconf->options.recurse)
fprintf(fp, " recurse");
if (resconf->options.smart)
fprintf(fp, " smart");
switch (resconf->options.tcp) {
case DNS_RESCONF_TCP_ENABLE:
break;
case DNS_RESCONF_TCP_ONLY:
fprintf(fp, " tcp");
break;
case DNS_RESCONF_TCP_SOCKS:
fprintf(fp, " tcp:socks");
break;
case DNS_RESCONF_TCP_DISABLE:
fprintf(fp, " tcp:disable");
break;
}
fputc('\n', fp);
if ((af = resconf->iface.ss_family) != AF_UNSPEC) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr);
fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface)));
}
return 0;
} /* dns_resconf_dump() */
/*
* H I N T S E R V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hints_soa {
unsigned char zone[DNS_D_MAXNAME + 1];
struct {
struct sockaddr_storage ss;
unsigned priority;
} addrs[16];
unsigned count;
struct dns_hints_soa *next;
}; /* struct dns_hints_soa */
struct dns_hints {
dns_atomic_t refcount;
struct dns_hints_soa *head;
}; /* struct dns_hints */
struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) {
static const struct dns_hints H_initializer;
struct dns_hints *H;
(void)resconf;
if (!(H = malloc(sizeof *H)))
goto syerr;
*H = H_initializer;
dns_hints_acquire(H);
return H;
syerr:
*error = dns_syerr();
free(H);
return 0;
} /* dns_hints_open() */
void dns_hints_close(struct dns_hints *H) {
struct dns_hints_soa *soa, *nxt;
if (!H || 1 != dns_hints_release(H))
return /* void */;
for (soa = H->head; soa; soa = nxt) {
nxt = soa->next;
free(soa);
}
free(H);
return /* void */;
} /* dns_hints_close() */
dns_refcount_t dns_hints_acquire(struct dns_hints *H) {
return dns_atomic_fetch_add(&H->refcount);
} /* dns_hints_acquire() */
dns_refcount_t dns_hints_release(struct dns_hints *H) {
return dns_atomic_fetch_sub(&H->refcount);
} /* dns_hints_release() */
struct dns_hints *dns_hints_mortal(struct dns_hints *hints) {
if (hints)
dns_hints_release(hints);
return hints;
} /* dns_hints_mortal() */
struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) {
struct dns_hints *hints = 0;
int error;
if (resconf)
dns_resconf_acquire(resconf);
else if (!(resconf = dns_resconf_local(&error)))
goto error;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
error = 0;
if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error)
goto error;
dns_resconf_close(resconf);
return hints;
error:
*error_ = error;
dns_resconf_close(resconf);
dns_hints_close(hints);
return 0;
} /* dns_hints_local() */
struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) {
static const struct {
int af;
char addr[INET6_ADDRSTRLEN];
} root_hints[] = {
{ AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */
{ AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */
{ AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */
};
struct dns_hints *hints = 0;
struct sockaddr_storage ss;
unsigned i;
int error, af;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
for (i = 0; i < lengthof(root_hints); i++) {
af = root_hints[i].af;
memset(&ss, 0, sizeof ss);
if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL))))
goto error;
*dns_sa_port(af, &ss) = htons(53);
ss.ss_family = af;
if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1)))
goto error;
}
return hints;
error:
*error_ = error;
dns_hints_close(hints);
return 0;
} /* dns_hints_root() */
static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) {
struct dns_hints_soa *soa;
for (soa = H->head; soa; soa = soa->next) {
if (0 == strcasecmp(zone, (char *)soa->zone))
return soa;
}
return 0;
} /* dns_hints_fetch() */
int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) {
static const struct dns_hints_soa soa_initializer;
struct dns_hints_soa *soa;
unsigned i;
if (!(soa = dns_hints_fetch(H, zone))) {
if (!(soa = malloc(sizeof *soa)))
return dns_syerr();
*soa = soa_initializer;
dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone);
soa->next = H->head;
H->head = soa;
}
i = soa->count % lengthof(soa->addrs);
memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa));
soa->addrs[i].priority = DNS_PP_MAX(1, priority);
if (soa->count < lengthof(soa->addrs))
soa->count++;
return 0;
} /* dns_hints_insert() */
static _Bool dns_hints_isinaddr_any(const void *sa) {
struct in_addr *addr;
if (dns_sa_family(sa) != AF_INET)
return 0;
addr = dns_sa_addr(AF_INET, sa, NULL);
return addr->s_addr == htonl(INADDR_ANY);
}
unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) {
unsigned i, n, p;
int error;
for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) {
union { struct sockaddr_in sin; } tmp;
struct sockaddr *ns;
/*
* dns_resconf_open initializes nameserver[0] to INADDR_ANY.
*
* Traditionally the semantics of 0.0.0.0 meant the default
* interface, which evolved to mean the loopback interface.
* See comment block preceding resolv/res_init.c:res_init in
* glibc 2.23. As of 2.23, glibc no longer translates
* 0.0.0.0 despite the code comment, but it does default to
* 127.0.0.1 when no nameservers are present.
*
* BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1.
* See lib/lwres/lwconfig.c:lwres_create_addr and the
* convert_zero flag. 127.0.0.1 is also the default when no
* nameservers are present.
*/
if (dns_hints_isinaddr_any(&resconf->nameserver[i])) {
memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin);
tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ns = (struct sockaddr *)&tmp.sin;
} else {
ns = (struct sockaddr *)&resconf->nameserver[i];
}
if ((error = dns_hints_insert(H, zone, ns, p)))
goto error;
p += !resconf->options.rotate;
}
return n;
error:
*error_ = error;
return n;
} /* dns_hints_insert_resconf() */
static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) {
int cmp;
if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority))
return cmp;
return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed);
} /* dns_hints_i_cmp() */
static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned p0, p;
p0 = 0;
for (p = 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) < 0)
p0 = p;
}
return p0;
} /* dns_hints_i_start() */
static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned pZ, p;
for (pZ = 0; pZ < soa->count; pZ++) {
if (dns_hints_i_cmp(pZ, p0, i, soa) > 0)
goto cont;
}
return soa->count;
cont:
for (p = pZ + 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) <= 0)
continue;
if (dns_hints_i_cmp(p, pZ, i, soa) >= 0)
continue;
pZ = p;
}
return pZ;
} /* dns_hints_i_skip() */
static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) {
static const struct dns_hints_i i_initializer;
struct dns_hints_soa *soa;
i->state = i_initializer.state;
do {
i->state.seed = dns_random();
} while (0 == i->state.seed);
if ((soa = dns_hints_fetch(hints, i->zone))) {
i->state.next = dns_hints_i_start(i, soa);
}
return i;
} /* dns_hints_i_init() */
unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) {
struct dns_hints_soa *soa;
unsigned n;
if (!(soa = dns_hints_fetch(H, i->zone)))
return 0;
n = 0;
while (i->state.next < soa->count && n < lim) {
*sa = (struct sockaddr *)&soa->addrs[i->state.next].ss;
*sa_len = dns_sa_len(*sa);
sa++;
sa_len++;
n++;
i->state.next = dns_hints_i_skip(i->state.next, i, soa);
}
return n;
} /* dns_hints_grep() */
struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *A, *P;
struct dns_rr rr;
char zone[DNS_D_MAXNAME + 1];
size_t zlen;
struct dns_hints_i i;
struct sockaddr *sa;
socklen_t slen;
int error;
struct dns_rr_i I_instance = { 0 };
I_instance.section = DNS_S_QUESTION;
if (!dns_rr_grep(&rr, 1, &I_instance, Q, &error))
goto error;
if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error)))
goto error;
else if (zlen >= sizeof zone)
goto toolong;
P = dns_p_init(&P_instance.p, 512);
dns_header(P)->qr = 1;
if ((error = dns_rr_copy(P, &rr, Q)))
goto error;
if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local.")))
goto error;
do {
i.zone = zone;
dns_hints_i_init(&i, hints);
while (dns_hints_grep(&sa, &slen, 1, &i, hints)) {
int af = sa->sa_family;
int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A;
if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL))))
goto error;
}
} while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen)));
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
return 0;
} /* dns_hints_query() */
/** ugly hack to support specifying ports other than 53 in resolv.conf. */
static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) {
struct dns_hints_soa *soa;
void *addrsoa;
socklen_t addrlen;
unsigned short port;
unsigned i;
for (soa = hints->head; soa; soa = soa->next) {
for (i = 0; i < soa->count; i++) {
if (af != soa->addrs[i].ss.ss_family)
continue;
if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen)))
continue;
if (memcmp(addr, addrsoa, addrlen))
continue;
port = *dns_sa_port(af, &soa->addrs[i].ss);
return (port)? port : htons(53);
}
}
return htons(53);
} /* dns_hints_port() */
int dns_hints_dump(struct dns_hints *hints, FILE *fp) {
struct dns_hints_soa *soa;
char addr[INET6_ADDRSTRLEN];
unsigned i;
int af, error;
for (soa = hints->head; soa; soa = soa->next) {
fprintf(fp, "ZONE \"%s\"\n", soa->zone);
for (i = 0; i < soa->count; i++) {
af = soa->addrs[i].ss.ss_family;
if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr)))
return error;
fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss)));
}
}
return 0;
} /* dns_hints_dump() */
/*
* C A C H E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) {
return dns_atomic_fetch_add(&cache->_.refcount);
} /* dns_cache_acquire() */
static dns_refcount_t dns_cache_release(struct dns_cache *cache) {
return dns_atomic_fetch_sub(&cache->_.refcount);
} /* dns_cache_release() */
static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) {
(void)query;
(void)cache;
(void)error;
return NULL;
} /* dns_cache_query() */
static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) {
(void)query;
(void)cache;
return 0;
} /* dns_cache_submit() */
static int dns_cache_check(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_check() */
static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) {
(void)cache;
(void)error;
return NULL;
} /* dns_cache_fetch() */
static int dns_cache_pollfd(struct dns_cache *cache) {
(void)cache;
return -1;
} /* dns_cache_pollfd() */
static short dns_cache_events(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_events() */
static void dns_cache_clear(struct dns_cache *cache) {
(void)cache;
return;
} /* dns_cache_clear() */
struct dns_cache *dns_cache_init(struct dns_cache *cache) {
static const struct dns_cache c_init = {
.acquire = &dns_cache_acquire,
.release = &dns_cache_release,
.query = &dns_cache_query,
.submit = &dns_cache_submit,
.check = &dns_cache_check,
.fetch = &dns_cache_fetch,
.pollfd = &dns_cache_pollfd,
.events = &dns_cache_events,
.clear = &dns_cache_clear,
._ = { .refcount = 1, },
};
*cache = c_init;
return cache;
} /* dns_cache_init() */
void dns_cache_close(struct dns_cache *cache) {
if (cache)
cache->release(cache);
} /* dns_cache_close() */
/*
* S O C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void dns_socketclose(int *fd, const struct dns_options *opts) {
if (opts && opts->closefd.cb)
opts->closefd.cb(fd, opts->closefd.arg);
if (*fd != -1) {
#if _WIN32
closesocket(*fd);
#else
close(*fd);
#endif
*fd = -1;
}
} /* dns_socketclose() */
#ifndef HAVE_IOCTLSOCKET
#define HAVE_IOCTLSOCKET (_WIN32 || _WIN64)
#endif
#ifndef HAVE_SOCK_CLOEXEC
#ifdef SOCK_CLOEXEC
#define HAVE_SOCK_CLOEXEC 1
#else
#define HAVE_SOCK_CLOEXEC 0
#endif
#endif
#ifndef HAVE_SOCK_NONBLOCK
#ifdef SOCK_NONBLOCK
#define HAVE_SOCK_NONBLOCK 1
#else
#define HAVE_SOCK_NONBLOCK 0
#endif
#endif
#define DNS_SO_MAXTRY 7
static int dns_socket(struct sockaddr *local, int type, int *error_) {
int fd = -1, flags, error;
#if defined FIONBIO
unsigned long opt;
#endif
flags = 0;
#if HAVE_SOCK_CLOEXEC
flags |= SOCK_CLOEXEC;
#endif
#if HAVE_SOCK_NONBLOCK
flags |= SOCK_NONBLOCK;
#endif
if (-1 == (fd = socket(local->sa_family, type|flags, 0)))
goto soerr;
#if defined F_SETFD && !HAVE_SOCK_CLOEXEC
if (-1 == fcntl(fd, F_SETFD, 1))
goto syerr;
#endif
#if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK
if (-1 == (flags = fcntl(fd, F_GETFL)))
goto syerr;
if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK))
goto syerr;
#elif defined FIONBIO && HAVE_IOCTLSOCKET
opt = 1;
if (0 != ioctlsocket(fd, FIONBIO, &opt))
goto soerr;
#endif
#if defined SO_NOSIGPIPE
if (type != SOCK_DGRAM) {
const int v = 1;
if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int)))
goto soerr;
}
#endif
if (local->sa_family != AF_INET && local->sa_family != AF_INET6)
return fd;
if (type != SOCK_DGRAM)
return fd;
#define LEAVE_SELECTION_OF_PORT_TO_KERNEL
#if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL)
/*
* FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by
* default. Though the ephemeral range is quite small on OS X
* (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu
* Xenial). See also RFC 6056.
*
* TODO: Optionally rely on the kernel to select a random port.
*/
if (*dns_sa_port(local->sa_family, local) == 0) {
struct sockaddr_storage tmp;
unsigned i, port;
memcpy(&tmp, local, dns_sa_len(local));
for (i = 0; i < DNS_SO_MAXTRY; i++) {
port = 1025 + (dns_random() % 64510);
*dns_sa_port(tmp.ss_family, &tmp) = htons(port);
if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp)))
return fd;
}
/* NB: continue to next bind statement */
}
#endif
if (0 == bind(fd, local, dns_sa_len(local)))
return fd;
/* FALL THROUGH */
soerr:
error = dns_soerr();
goto error;
#if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK)
syerr:
error = dns_syerr();
goto error;
#endif
error:
*error_ = error;
dns_socketclose(&fd, NULL);
return -1;
} /* dns_socket() */
enum {
DNS_SO_UDP_INIT = 1,
DNS_SO_UDP_CONN,
DNS_SO_UDP_SEND,
DNS_SO_UDP_RECV,
DNS_SO_UDP_DONE,
DNS_SO_TCP_INIT,
DNS_SO_TCP_CONN,
DNS_SO_TCP_SEND,
DNS_SO_TCP_RECV,
DNS_SO_TCP_DONE,
DNS_SO_SOCKS_INIT,
DNS_SO_SOCKS_CONN,
DNS_SO_SOCKS_HELLO_SEND,
DNS_SO_SOCKS_HELLO_RECV,
DNS_SO_SOCKS_AUTH_SEND,
DNS_SO_SOCKS_AUTH_RECV,
DNS_SO_SOCKS_REQUEST_PREPARE,
DNS_SO_SOCKS_REQUEST_SEND,
DNS_SO_SOCKS_REQUEST_RECV,
DNS_SO_SOCKS_REQUEST_RECV_V6,
DNS_SO_SOCKS_HANDSHAKE_DONE,
};
struct dns_socket {
struct dns_options opts;
int udp;
int tcp;
int *old;
unsigned onum, olim;
int type;
struct sockaddr_storage local, remote;
struct dns_k_permutor qids;
struct dns_stat stat;
struct dns_trace *trace;
/*
* NOTE: dns_so_reset() zeroes everything from here down.
*/
int state;
unsigned short qid;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_packet *query;
size_t qout;
/* During a SOCKS handshake the query is temporarily stored
* here. */
struct dns_packet *query_backup;
struct dns_clock elapsed;
struct dns_packet *answer;
size_t alen, apos;
}; /* struct dns_socket */
/*
* NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have
* a chance to recognize a state change after installing a persistent event
* and where sequential descriptors with the same integer value returned
* from _pollfd() would be ambiguous. See dns_so_closefds().
*/
static int dns_so_closefd(struct dns_socket *so, int *fd) {
int error;
if (*fd == -1)
return 0;
if (so->opts.closefd.cb) {
if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) {
return error;
} else if (*fd == -1)
return 0;
}
if (!(so->onum < so->olim)) {
unsigned olim = DNS_PP_MAX(4, so->olim * 2);
void *old;
if (!(old = realloc(so->old, sizeof so->old[0] * olim)))
return dns_syerr();
so->old = old;
so->olim = olim;
}
so->old[so->onum++] = *fd;
*fd = -1;
return 0;
} /* dns_so_closefd() */
#define DNS_SO_CLOSE_UDP 0x01
#define DNS_SO_CLOSE_TCP 0x02
#define DNS_SO_CLOSE_OLD 0x04
#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD)
static void dns_so_closefds(struct dns_socket *so, int which) {
if (DNS_SO_CLOSE_UDP & which)
dns_socketclose(&so->udp, &so->opts);
if (DNS_SO_CLOSE_TCP & which)
dns_socketclose(&so->tcp, &so->opts);
if (DNS_SO_CLOSE_OLD & which) {
unsigned i;
for (i = 0; i < so->onum; i++)
dns_socketclose(&so->old[i], &so->opts);
so->onum = 0;
free(so->old);
so->old = 0;
so->olim = 0;
}
} /* dns_so_closefds() */
static void dns_so_destroy(struct dns_socket *);
static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, };
*so = so_initializer;
so->type = type;
if (opts)
so->opts = *opts;
if (local)
memcpy(&so->local, local, dns_sa_len(local));
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error)))
goto error;
dns_k_permutor_init(&so->qids, 1, 65535);
return so;
error:
dns_so_destroy(so);
return 0;
} /* dns_so_init() */
struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
struct dns_socket *so;
if (!(so = malloc(sizeof *so)))
goto syerr;
if (!dns_so_init(so, local, type, opts, error))
goto error;
return so;
syerr:
*error = dns_syerr();
error:
dns_so_close(so);
return 0;
} /* dns_so_open() */
static void dns_so_destroy(struct dns_socket *so) {
dns_so_reset(so);
dns_so_closefds(so, DNS_SO_CLOSE_ALL);
dns_trace_close(so->trace);
} /* dns_so_destroy() */
void dns_so_close(struct dns_socket *so) {
if (!so)
return;
dns_so_destroy(so);
free(so);
} /* dns_so_close() */
void dns_so_reset(struct dns_socket *so) {
dns_p_setptr(&so->answer, NULL);
memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state));
} /* dns_so_reset() */
unsigned short dns_so_mkqid(struct dns_socket *so) {
return dns_k_permutor_step(&so->qids);
} /* dns_so_mkqid() */
#define DNS_SO_MINBUF 768
static int dns_so_newanswer(struct dns_socket *so, size_t len) {
size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF);
void *p;
if (!(p = realloc(so->answer, size)))
return dns_syerr();
so->answer = dns_p_init(p, size);
return 0;
} /* dns_so_newanswer() */
int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) {
struct dns_rr rr;
int error = DNS_EUNKNOWN;
dns_so_reset(so);
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error)))
goto error;
/*
* NOTE: Don't bail if expansion is too long; caller may be
* intentionally sending long names. However, we won't be able to
* verify it on return.
*/
so->qtype = rr.type;
so->qclass = rr.class;
if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF)))
goto syerr;
memcpy(&so->remote, host, dns_sa_len(host));
so->query = Q;
so->qout = 0;
dns_begin(&so->elapsed);
if (dns_header(so->query)->qid == 0)
dns_header(so->query)->qid = dns_so_mkqid(so);
so->qid = dns_header(so->query)->qid;
so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT :
(so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT;
so->stat.queries++;
dns_trace_so_submit(so->trace, Q, host, 0);
return 0;
syerr:
error = dns_syerr();
error:
dns_so_reset(so);
dns_trace_so_submit(so->trace, Q, host, error);
return error;
} /* dns_so_submit() */
static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) {
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error = -1;
if (P->end < 12)
goto reject;
if (so->qid != dns_header(P)->qid)
goto reject;
if (!dns_p_count(P, DNS_S_QD))
goto reject;
if (0 != dns_rr_parse(&rr, 12, P))
goto reject;
if (rr.type != so->qtype || rr.class != so->qclass)
goto reject;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error)))
goto error;
else if (qlen >= sizeof qname || qlen != so->qlen)
goto reject;
if (0 != strcasecmp(so->qname, qname))
goto reject;
dns_trace_so_verify(so->trace, P, 0);
return 0;
reject:
error = DNS_EVERIFY;
error:
DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error));
dns_trace_so_verify(so->trace, P, error);
return error;
} /* dns_so_verify() */
static _Bool dns_so_tcp_keep(struct dns_socket *so) {
struct sockaddr_storage remote;
socklen_t l = sizeof remote;
if (so->tcp == -1)
return 0;
if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l))
return 0;
return 0 == dns_sa_cmp(&remote, &so->remote);
} /* dns_so_tcp_keep() */
/* Convenience functions for sending non-DNS data. */
/* Set up everything for sending LENGTH octets. Returns the buffer
for the data. */
static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->qout = 2;
so->query->end = length;
return so->query->data;
}
/* Set up everything for receiving LENGTH octets. */
static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->apos = 2;
so->alen = length;
}
/* Returns the buffer containing the received data. */
static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) {
return so->answer->data;
}
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#elif defined __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warray-bounds"
#endif
static int dns_so_tcp_send(struct dns_socket *so) {
unsigned char *qsrc;
size_t qend;
int error;
size_t n;
so->query->data[-2] = 0xff & (so->query->end >> 8);
so->query->data[-1] = 0xff & (so->query->end >> 0);
qend = so->query->end + 2;
while (so->qout < qend) {
qsrc = &so->query->data[-2] + so->qout;
n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error);
dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error);
if (error)
return error;
so->qout += n;
so->stat.tcp.sent.bytes += n;
}
so->stat.tcp.sent.count++;
return 0;
} /* dns_so_tcp_send() */
static int dns_so_tcp_recv(struct dns_socket *so) {
unsigned char *asrc;
size_t aend, alen, n;
int error;
aend = so->alen + 2;
while (so->apos < aend) {
asrc = &so->answer->data[-2];
n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error);
dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error);
if (error)
return error;
so->apos += n;
so->stat.tcp.rcvd.bytes += n;
if (so->alen == 0 && so->apos >= 2) {
alen = ((0xff & so->answer->data[-2]) << 8)
| ((0xff & so->answer->data[-1]) << 0);
if ((error = dns_so_newanswer(so, alen)))
return error;
so->alen = alen;
aend = alen + 2;
}
}
so->answer->end = so->alen;
so->stat.tcp.rcvd.count++;
return 0;
} /* dns_so_tcp_recv() */
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic pop
#elif __clang__
# pragma clang diagnostic pop
#endif
int dns_so_check(struct dns_socket *so) {
int error;
size_t n;
unsigned char *buffer;
retry:
switch (so->state) {
case DNS_SO_UDP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error)))
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_CONN:
udp_connect_retry:
error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error);
/* Linux returns EINVAL when address was bound to
localhost and it's external IP address now. */
if (error == EINVAL) {
struct sockaddr unspec_addr;
memset (&unspec_addr, 0, sizeof unspec_addr);
unspec_addr.sa_family = AF_UNSPEC;
connect(so->udp, &unspec_addr, sizeof unspec_addr);
goto udp_connect_retry;
} else if (error == ECONNREFUSED)
/* Error for previous socket operation may
- be reserverd(?) asynchronously. */
+ be reserved(?) asynchronously. */
goto udp_connect_retry;
if (error)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_SEND:
n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error);
dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error);
if (error)
goto error;
so->stat.udp.sent.bytes += n;
so->stat.udp.sent.count++;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_RECV:
n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error);
dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error);
if (error)
goto error;
so->answer->end = n;
so->stat.udp.rcvd.bytes += n;
so->stat.udp.rcvd.count++;
if ((error = dns_so_verify(so, so->answer)))
goto trash;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_DONE:
if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM)
return 0;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
}
if (dns_so_tcp_keep(so)) {
so->state = DNS_SO_TCP_SEND;
goto retry;
}
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_CONN:
error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error);
if (error && error != DNS_EISCONN)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_DONE:
/* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */
if (so->type != SOCK_STREAM) {
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
}
if ((error = dns_so_verify(so, so->answer)))
goto error;
return 0;
case DNS_SO_SOCKS_INIT:
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_CONN: {
unsigned char method;
error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error);
if (error && error != DNS_EISCONN)
goto error;
/* We need to do a handshake with the SOCKS server,
* but the query is already in the buffer. Move it
* out of the way. */
dns_p_movptr(&so->query_backup, &so->query);
/* Create a new buffer for the handshake. */
dns_p_grow(&so->query);
/* Negotiate method. */
buffer = dns_so_tcp_send_buffer(so, 3);
buffer[0] = 5; /* RFC-1928 VER field. */
buffer[1] = 1; /* NMETHODS */
if (so->opts.socks_user)
method = 2; /* Method: username/password authentication. */
else
method = 0; /* Method: No authentication required. */
buffer[2] = method;
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_RECV: {
unsigned char method;
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
method = so->opts.socks_user ? 2 : 0;
if (buffer[0] != 5 || buffer[1] != method) {
/* Socks server returned wrong version or does
not support our requested method. */
error = ENOTSUP; /* Fixme: Is there a better errno? */
goto error;
}
if (method == 0) {
/* No authentication, go ahead and send the
request. */
so->state = DNS_SO_SOCKS_REQUEST_PREPARE;
goto retry;
}
/* Prepare username/password sub-negotiation. */
if (! so->opts.socks_password) {
error = EINVAL; /* No password given. */
goto error;
} else {
size_t buflen, ulen, plen;
ulen = strlen(so->opts.socks_user);
plen = strlen(so->opts.socks_password);
if (!ulen || ulen > 255 || !plen || plen > 255) {
error = EINVAL; /* Credentials too long or too short. */
goto error;
}
buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen);
buffer[0] = 1; /* VER of the sub-negotiation. */
buffer[1] = (unsigned char) ulen;
buflen = 2;
memcpy (buffer+buflen, so->opts.socks_user, ulen);
buflen += ulen;
buffer[buflen++] = (unsigned char) plen;
memcpy (buffer+buflen, so->opts.socks_password, plen);
}
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Skip the two length octets, and receive two octets. */
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 1) {
/* SOCKS server returned wrong version. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
/* SOCKS server denied access. */
error = EACCES;
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_PREPARE:
/* Send request details (rfc-1928, 4). */
buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10);
buffer[0] = 5; /* VER */
buffer[1] = 1; /* CMD = CONNECT */
buffer[2] = 0; /* RSV */
if (so->remote.ss_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote;
buffer[3] = 4; /* ATYP = IPv6 */
memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */
} else {
struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote;
buffer[3] = 1; /* ATYP = IPv4 */
memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Expect ten octets. This is the length of the
* response assuming a IPv4 address is used. */
dns_so_tcp_recv_expect(so, 10);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 5 || buffer[2] != 0) {
/* Socks server returned wrong version or the
reserved field is not zero. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
switch (buffer[1]) {
case 0x01: /* general SOCKS server failure. */
error = ENETDOWN;
break;
case 0x02: /* connection not allowed by ruleset. */
error = EACCES;
break;
case 0x03: /* Network unreachable */
error = ENETUNREACH;
break;
case 0x04: /* Host unreachable */
error = EHOSTUNREACH;
break;
case 0x05: /* Connection refused */
error = ECONNREFUSED;
break;
case 0x06: /* TTL expired */
error = ETIMEDOUT;
break;
case 0x08: /* Address type not supported */
error = EPROTONOSUPPORT;
break;
case 0x07: /* Command not supported */
default:
error = ENOTSUP; /* Fixme: Is there a better error? */
break;
}
goto error;
}
if (buffer[3] == 1) {
/* This was indeed an IPv4 address. */
so->state = DNS_SO_SOCKS_HANDSHAKE_DONE;
goto retry;
}
if (buffer[3] != 4) {
error = ENOTSUP;
goto error;
}
/* Expect receive twelve octets. This accounts for
* the remaining bytes assuming an IPv6 address is
* used. */
dns_so_tcp_recv_expect(so, 12);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV_V6:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HANDSHAKE_DONE:
/* We have not way to store the actual address used by
* the server. Then again, we don't really care. */
/* Restore the query. */
dns_p_movptr(&so->query, &so->query_backup);
/* Reset cursors. */
so->qout = 0;
so->apos = 0;
so->alen = 0;
/* SOCKS handshake is done. Proceed with the
* lookup. */
so->state = DNS_SO_TCP_SEND;
goto retry;
default:
error = DNS_EUNKNOWN;
goto error;
} /* switch() */
trash:
DNS_CARP("discarding packet");
goto retry;
error:
switch (error) {
case DNS_EINTR:
goto retry;
case DNS_EINPROGRESS:
/* FALL THROUGH */
case DNS_EALREADY:
/* FALL THROUGH */
#if DNS_EWOULDBLOCK != DNS_EAGAIN
case DNS_EWOULDBLOCK:
/* FALL THROUGH */
#endif
error = DNS_EAGAIN;
break;
} /* switch() */
return error;
} /* dns_so_check() */
struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) {
struct dns_packet *answer;
switch (so->state) {
case DNS_SO_UDP_DONE:
case DNS_SO_TCP_DONE:
answer = so->answer;
so->answer = 0;
dns_trace_so_fetch(so->trace, answer, 0);
return answer;
default:
*error = DNS_EUNKNOWN;
dns_trace_so_fetch(so->trace, NULL, *error);
return 0;
}
} /* dns_so_fetch() */
struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) {
struct dns_packet *A;
int error;
if (!so->state) {
if ((error = dns_so_submit(so, Q, host)))
goto error;
}
if ((error = dns_so_check(so)))
goto error;
if (!(A = dns_so_fetch(so, &error)))
goto error;
dns_so_reset(so);
return A;
error:
*error_ = error;
return 0;
} /* dns_so_query() */
time_t dns_so_elapsed(struct dns_socket *so) {
return dns_elapsed(&so->elapsed);
} /* dns_so_elapsed() */
void dns_so_clear(struct dns_socket *so) {
dns_so_closefds(so, DNS_SO_CLOSE_OLD);
} /* dns_so_clear() */
static int dns_so_events2(struct dns_socket *so, enum dns_events type) {
int events = 0;
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_UDP_RECV:
events |= DNS_POLLIN;
break;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_TCP_RECV:
events |= DNS_POLLIN;
break;
} /* switch() */
switch (type) {
case DNS_LIBEVENT:
return DNS_POLL2EV(events);
default:
return events;
} /* switch() */
} /* dns_so_events2() */
int dns_so_events(struct dns_socket *so) {
return dns_so_events2(so, so->opts.events);
} /* dns_so_events() */
int dns_so_pollfd(struct dns_socket *so) {
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
case DNS_SO_UDP_RECV:
return so->udp;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
case DNS_SO_TCP_RECV:
return so->tcp;
} /* switch() */
return -1;
} /* dns_so_pollfd() */
int dns_so_poll(struct dns_socket *so, int timeout) {
return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout);
} /* dns_so_poll() */
const struct dns_stat *dns_so_stat(struct dns_socket *so) {
return &so->stat;
} /* dns_so_stat() */
struct dns_trace *dns_so_trace(struct dns_socket *so) {
return so->trace;
} /* dns_so_trace() */
void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) {
struct dns_trace *otrace = so->trace;
so->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
} /* dns_so_settrace() */
/*
* R E S O L V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
enum dns_res_state {
DNS_R_INIT,
DNS_R_GLUE,
DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */
DNS_R_FILE, /* Lookup in local hosts database */
DNS_R_CACHE, /* Lookup in application cache */
DNS_R_SUBMIT,
DNS_R_CHECK,
DNS_R_FETCH,
DNS_R_BIND, /* Lookup in the network */
DNS_R_SEARCH,
DNS_R_HINTS,
DNS_R_ITERATE,
DNS_R_FOREACH_NS,
DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */
DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */
DNS_R_FOREACH_A,
DNS_R_QUERY_A,
DNS_R_FOREACH_AAAA,
DNS_R_QUERY_AAAA,
DNS_R_CNAME0_A,
DNS_R_CNAME1_A,
DNS_R_FINISH,
DNS_R_SMART0_A,
DNS_R_SMART1_A,
DNS_R_DONE,
DNS_R_SERVFAIL,
}; /* enum dns_res_state */
#define DNS_R_MAXDEPTH 8
#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1)
struct dns_resolver {
struct dns_socket so;
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_hints *hints;
struct dns_cache *cache;
struct dns_trace *trace;
dns_atomic_t refcount;
/* Reset zeroes everything below here. */
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_clock elapsed;
dns_resconf_i_t search;
struct dns_rr_i smart;
struct dns_packet *nodata; /* answer if nothing better */
unsigned sp;
struct dns_res_frame {
enum dns_res_state state;
int error;
int which; /* (B)IND, (F)ILE; index into resconf->lookup */
int qflags;
unsigned attempts;
struct dns_packet *query, *answer, *hints;
struct dns_rr_i hints_i, hints_j;
struct dns_rr hints_ns, ans_cname;
} stack[DNS_R_MAXDEPTH];
}; /* struct dns_resolver */
static int dns_res_tcp2type(int tcp) {
switch (tcp) {
case DNS_RESCONF_TCP_ONLY:
case DNS_RESCONF_TCP_SOCKS:
return SOCK_STREAM;
case DNS_RESCONF_TCP_DISABLE:
return SOCK_DGRAM;
default:
return 0;
}
} /* dns_res_tcp2type() */
struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) {
static const struct dns_resolver R_initializer
= { .refcount = 1, };
struct dns_resolver *R = 0;
int type, error;
/*
* Grab ref count early because the caller may have passed us a mortal
* reference, and we want to do the right thing if we return early
* from an error.
*/
if (resconf)
dns_resconf_acquire(resconf);
if (hosts)
dns_hosts_acquire(hosts);
if (hints)
dns_hints_acquire(hints);
if (cache)
dns_cache_acquire(cache);
/*
* Don't try to load it ourselves because a NULL object might be an
* error from, say, dns_resconf_root(), and loading
- * dns_resconf_local() by default would create undesirable surpises.
+ * dns_resconf_local() by default would create undesirable surprises.
*/
if (!resconf || !hosts || !hints) {
if (!*_error)
*_error = EINVAL;
goto _error;
}
if (!(R = malloc(sizeof *R)))
goto syerr;
*R = R_initializer;
type = dns_res_tcp2type(resconf->options.tcp);
if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error))
goto error;
R->resconf = resconf;
R->hosts = hosts;
R->hints = hints;
R->cache = cache;
return R;
syerr:
error = dns_syerr();
error:
*_error = error;
_error:
dns_res_close(R);
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
dns_cache_close(cache);
return 0;
} /* dns_res_open() */
struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) {
struct dns_resolv_conf *resconf = 0;
struct dns_hosts *hosts = 0;
struct dns_hints *hints = 0;
struct dns_resolver *res = 0;
if (!(resconf = dns_resconf_local(error)))
goto epilog;
if (!(hosts = dns_hosts_local(error)))
goto epilog;
if (!(hints = dns_hints_local(resconf, error)))
goto epilog;
if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error)))
goto epilog;
epilog:
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
return res;
} /* dns_res_stub() */
static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) {
(void)R;
dns_p_setptr(&frame->query, NULL);
dns_p_setptr(&frame->answer, NULL);
dns_p_setptr(&frame->hints, NULL);
} /* dns_res_frame_destroy() */
static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) {
memset(frame, '\0', sizeof *frame);
/*
* NB: Can be invoked from dns_res_open, before R->resconf has been
* initialized.
*/
if (R->resconf) {
if (!R->resconf->options.recurse)
frame->qflags |= DNS_Q_RD;
if (R->resconf->options.edns0)
frame->qflags |= DNS_Q_EDNS0;
}
} /* dns_res_frame_init() */
static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) {
dns_res_frame_destroy(R, frame);
dns_res_frame_init(R, frame);
} /* dns_res_frame_reset() */
static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) {
struct dns_packet *P = NULL;
if (!(F < endof(R->stack)))
return DNS_EUNKNOWN;
dns_p_movptr(&P, &F->query);
dns_res_frame_reset(R, F);
dns_p_movptr(&F->query, &P);
return dns_q_make(&F->query, qname, qtype, qclass, F->qflags);
} /* dns_res_frame_prepare() */
void dns_res_reset(struct dns_resolver *R) {
unsigned i;
dns_so_reset(&R->so);
dns_p_setptr(&R->nodata, NULL);
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_destroy(R, &R->stack[i]);
memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname));
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_init(R, &R->stack[i]);
} /* dns_res_reset() */
void dns_res_close(struct dns_resolver *R) {
if (!R || 1 < dns_res_release(R))
return;
dns_res_reset(R);
dns_so_destroy(&R->so);
dns_hints_close(R->hints);
dns_hosts_close(R->hosts);
dns_resconf_close(R->resconf);
dns_cache_close(R->cache);
dns_trace_close(R->trace);
free(R);
} /* dns_res_close() */
dns_refcount_t dns_res_acquire(struct dns_resolver *R) {
return dns_atomic_fetch_add(&R->refcount);
} /* dns_res_acquire() */
dns_refcount_t dns_res_release(struct dns_resolver *R) {
return dns_atomic_fetch_sub(&R->refcount);
} /* dns_res_release() */
struct dns_resolver *dns_res_mortal(struct dns_resolver *res) {
if (res)
dns_res_release(res);
return res;
} /* dns_res_mortal() */
static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) {
size_t bufsiz = P0->end + P1->end;
struct dns_packet *P[3] = { P0, P1, 0 };
struct dns_rr rr[3];
int error, copy, i;
enum dns_section section;
retry:
if (!(P[2] = dns_p_make(bufsiz, &error)))
goto error;
dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) {
if ((error = dns_rr_copy(P[2], &rr[0], P[0])))
goto error;
}
for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) {
for (i = 0; i < 2; i++) {
dns_rr_foreach(&rr[i], P[i], .section = section) {
copy = 1;
dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) {
if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) {
copy = 0;
break;
}
}
if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) {
if (error == DNS_ENOBUFS && bufsiz < 65535) {
dns_p_setptr(&P[2], NULL);
bufsiz = DNS_PP_MAX(65535, bufsiz * 2);
goto retry;
}
goto error;
}
} /* foreach(rr) */
} /* foreach(packet) */
} /* foreach(section) */
return P[2];
error:
*error_ = error;
dns_p_free(P[2]);
return 0;
} /* dns_res_merge() */
static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
struct dns_rr rr;
unsigned sp;
int error;
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error))
|| qlen >= sizeof qname)
return 0;
if (!(qtype = dns_rr_type(12, Q)))
return 0;
if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0)))
return 0;
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (dns_p_count(P, DNS_S_AN) > 0)
goto copy;
/* Otherwise, look for a CNAME */
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (!dns_p_count(P, DNS_S_AN))
return 0;
copy:
return dns_p_copy(dns_p_make(P->end, &error), P);
} /* dns_res_glue() */
/*
* Sort NS records by three criteria:
*
* 1) Whether glue is present.
* 2) Whether glue record is original or of recursive lookup.
* 3) Randomly shuffle records which share the above criteria.
*
* NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will
* be added during an iteration.
*
* FIXME: Only groks A glue, not AAAA glue.
*/
static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
_Bool glued[2] = { 0 };
struct dns_rr x = { 0 }, y = { 0 };
struct dns_ns ns;
int cmp, error;
if (!(error = dns_ns_parse(&ns, a, P))) {
struct dns_rr_i I_instance = { 0 };
I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
I_instance.name = ns.host;
I_instance.type = DNS_T_A;
glued[0] = !!dns_rr_grep(&x, 1, &I_instance, P, &error);
}
if (!(error = dns_ns_parse(&ns, b, P))) {
struct dns_rr_i I_instance = { 0 };
I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
I_instance.name = ns.host;
I_instance.type = DNS_T_A;
glued[1] = !!dns_rr_grep(&y, 1, &I_instance, P, &error);
}
if ((cmp = glued[1] - glued[0])) {
return cmp;
} else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) {
return cmp;
} else {
return dns_rr_i_shuffle(a, b, i, P);
}
} /* dns_res_nameserv_cmp() */
#define dgoto(sp, i) \
do { R->stack[(sp)].state = (i); goto exec; } while (0)
static int dns_res_exec(struct dns_resolver *R) {
struct dns_res_frame *F;
struct dns_packet *P;
union {
char host[DNS_D_MAXNAME + 1];
char name[DNS_D_MAXNAME + 1];
struct dns_ns ns;
struct dns_cname cname;
} u;
size_t len;
struct dns_rr rr;
int error;
exec:
F = &R->stack[R->sp];
switch (F->state) {
case DNS_R_INIT:
F->state++; /* FALL THROUGH */
case DNS_R_GLUE:
if (R->sp == 0)
dgoto(R->sp, DNS_R_SWITCH);
if (!F->query)
goto noquery;
if (!(F->answer = dns_res_glue(R, F->query)))
dgoto(R->sp, DNS_R_SWITCH);
if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) {
dgoto(R->sp, DNS_R_FINISH);
}
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
F->state++;
case DNS_R_SWITCH:
while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) {
switch (R->resconf->lookup[F->which++]) {
case 'b': case 'B':
dgoto(R->sp, DNS_R_BIND);
case 'f': case 'F':
dgoto(R->sp, DNS_R_FILE);
case 'c': case 'C':
if (R->cache)
dgoto(R->sp, DNS_R_CACHE);
break;
default:
break;
}
}
/*
* FIXME: Examine more closely whether our logic is correct
* and DNS_R_SERVFAIL is the correct default response.
*
* Case 1: We got here because we never got an answer on the
* wire. All queries timed-out and we reached maximum
* attempts count. See DNS_R_FOREACH_NS. In that case
* DNS_R_SERVFAIL is the correct state, unless we want to
* return DNS_ETIMEDOUT.
*
* Case 2: We were a stub resolver and got an unsatisfactory
* answer (empty ANSWER section) which caused us to jump
* back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We
* return the answer returned from the wire, which we
* stashed in R->nodata.
*
* Case 3: We reached maximum attempts count as in case #1,
* but never got an authoritative response which caused us
* to short-circuit. See end of DNS_R_QUERY_A case. We
* should probably prepare R->nodata as in case #2.
*/
if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */
dns_p_movptr(&F->answer, &R->nodata);
dgoto(R->sp, DNS_R_FINISH);
}
dgoto(R->sp, DNS_R_SERVFAIL);
case DNS_R_FILE:
if (R->sp > 0) {
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
} else {
R->search = 0;
while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) {
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
}
}
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_CACHE:
error = 0;
if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags)))
goto error;
if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_SUBMIT:
if ((error = R->cache->submit(F->query, R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_CHECK:
if ((error = R->cache->check(R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_FETCH:
error = 0;
if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_BIND:
if (R->sp > 0) {
if (!F->query)
goto noquery;
dgoto(R->sp, DNS_R_HINTS);
}
R->search = 0;
F->state++; /* FALL THROUGH */
case DNS_R_SEARCH:
/*
* XXX: We probably should only apply the domain search
* algorithm if R->sp == 0.
*/
if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search)))
dgoto(R->sp, DNS_R_SWITCH);
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_HINTS:
if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_ITERATE:
dns_rr_i_init(&F->hints_i);
F->hints_i.section = DNS_S_AUTHORITY;
F->hints_i.type = DNS_T_NS;
F->hints_i.sort = &dns_res_nameserv_cmp;
F->hints_i.args[0] = F->hints->end;
F->state++; /* FALL THROUGH */
case DNS_R_FOREACH_NS:
dns_rr_i_save(&F->hints_i);
/* Load our next nameserver host. */
if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) {
if (++F->attempts < R->resconf->options.attempts)
dgoto(R->sp, DNS_R_ITERATE);
dgoto(R->sp, DNS_R_SWITCH);
}
dns_rr_i_init(&F->hints_j);
/* Assume there are glue records */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_RESOLV0_NS:
/* Have we reached our max depth? */
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FOREACH_NS);
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_RESOLV1_NS:
if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error)))
goto error;
else if (len >= sizeof u.host)
goto toolong;
dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AR;
if ((error = dns_rr_copy(F->hints, &rr, F[1].answer)))
goto error;
dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */
}
dgoto(R->sp, DNS_R_FOREACH_NS);
case DNS_R_FOREACH_A: {
struct dns_a a;
struct sockaddr_in sin;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv6 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_a_parse(&a, &rr, F->hints)))
goto error;
memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */
sin.sin_family = AF_INET;
sin.sin_addr = a.addr;
if (R->sp == 0)
sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr);
else
sin.sin_port = htons(53);
if (DNS_DEBUG) {
char addr[INET_ADDRSTRLEN + 1];
dns_a_print(addr, sizeof addr, &a);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_A:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_A);
error = dns_so_check(&R->so);
if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_A);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_A);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_FOREACH_AAAA: {
struct dns_aaaa aaaa;
struct sockaddr_in6 sin6;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv4 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_A);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints)))
goto error;
memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = aaaa.addr;
if (R->sp == 0)
sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr);
else
sin6.sin6_port = htons(53);
if (DNS_DEBUG) {
char addr[INET6_ADDRSTRLEN + 1];
dns_aaaa_print(addr, sizeof addr, &aaaa);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_AAAA:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_AAAA);
error = dns_so_check(&R->so);
if (error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_AAAA);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_AAAA);
case DNS_R_CNAME0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FINISH);
if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_CNAME1_A:
if (!(P = dns_res_merge(F->answer, F[1].answer, &error)))
goto error;
dns_p_setptr(&F->answer, P);
dgoto(R->sp, DNS_R_FINISH);
case DNS_R_FINISH:
if (!F->answer)
goto noanswer;
if (!R->resconf->options.smart || R->sp > 0)
dgoto(R->sp, DNS_R_DONE);
R->smart.section = DNS_S_AN;
R->smart.type = R->qtype;
dns_rr_i_init(&R->smart);
F->state++; /* FALL THROUGH */
case DNS_R_SMART0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_DONE);
while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) {
union {
struct dns_ns ns;
struct dns_mx mx;
struct dns_srv srv;
} rd;
const char *qname;
enum dns_type qtype;
enum dns_class qclass;
switch (rr.type) {
case DNS_T_NS:
if ((error = dns_ns_parse(&rd.ns, &rr, F->answer)))
goto error;
qname = rd.ns.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_MX:
if ((error = dns_mx_parse(&rd.mx, &rr, F->answer)))
goto error;
qname = rd.mx.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_SRV:
if ((error = dns_srv_parse(&rd.srv, &rr, F->answer)))
goto error;
qname = rd.srv.target;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
default:
continue;
} /* switch() */
if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
} /* while() */
/*
* NOTE: SMTP specification says to fallback to A record.
*
* XXX: Should we add a mock MX answer?
*/
if (R->qtype == DNS_T_MX && R->smart.state.count == 0) {
if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN)))
goto error;
R->smart.state.count++;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
}
dgoto(R->sp, DNS_R_DONE);
case DNS_R_SMART1_A:
if (!F[1].answer)
goto noanswer;
/*
* FIXME: For CNAME chains (which are typically illegal in
* this context), we should rewrite the record host name
* to the original smart qname. All the user cares about
* is locating that A/AAAA record.
*/
dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) {
rr.section = DNS_S_AR;
if (dns_rr_exists(&rr, F[1].answer, F->answer))
continue;
while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) {
if (error != DNS_ENOBUFS)
goto error;
if ((error = dns_p_grow(&F->answer)))
goto error;
}
}
dgoto(R->sp, DNS_R_SMART0_A);
case DNS_R_DONE:
if (!F->answer)
goto noanswer;
if (R->sp > 0)
dgoto(--R->sp, F[-1].state);
break;
case DNS_R_SERVFAIL:
if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error)))
goto error;
dns_header(F->answer)->qr = 1;
dns_header(F->answer)->rcode = DNS_RC_SERVFAIL;
if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0)))
goto error;
dgoto(R->sp, DNS_R_DONE);
default:
error = EINVAL;
goto error;
} /* switch () */
return 0;
noquery:
error = DNS_ENOQUERY;
goto error;
noanswer:
error = DNS_ENOANSWER;
goto error;
toolong:
error = DNS_EILLEGAL;
/* FALL THROUGH */
error:
return error;
} /* dns_res_exec() */
#undef goto
void dns_res_clear(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
R->cache->clear(R->cache);
break;
default:
dns_so_clear(&R->so);
break;
}
} /* dns_res_clear() */
static int dns_res_events2(struct dns_resolver *R, enum dns_events type) {
int events;
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
events = R->cache->events(R->cache);
return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events;
default:
return dns_so_events2(&R->so, type);
}
} /* dns_res_events2() */
int dns_res_events(struct dns_resolver *R) {
return dns_res_events2(R, R->so.opts.events);
} /* dns_res_events() */
int dns_res_pollfd(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
return R->cache->pollfd(R->cache);
default:
return dns_so_pollfd(&R->so);
}
} /* dns_res_pollfd() */
time_t dns_res_timeout(struct dns_resolver *R) {
time_t elapsed;
switch (R->stack[R->sp].state) {
#if 0
case DNS_R_QUERY_AAAA:
#endif
case DNS_R_QUERY_A:
elapsed = dns_so_elapsed(&R->so);
if (elapsed <= dns_resconf_timeout(R->resconf))
return R->resconf->options.timeout - elapsed;
break;
default:
break;
} /* switch() */
/*
* NOTE: We're not in a pollable state, or the user code hasn't
* called dns_res_check properly. The calling code is probably
* broken. Put them into a slow-burn pattern.
*/
return 1;
} /* dns_res_timeout() */
time_t dns_res_elapsed(struct dns_resolver *R) {
return dns_elapsed(&R->elapsed);
} /* dns_res_elapsed() */
int dns_res_poll(struct dns_resolver *R, int timeout) {
return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout);
} /* dns_res_poll() */
int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) {
dns_res_reset(R);
/* Don't anchor; that can conflict with searchlist generation. */
dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0);
R->qtype = qtype;
R->qclass = qclass;
dns_begin(&R->elapsed);
dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0);
return 0;
} /* dns_res_submit2() */
int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) {
return dns_res_submit2(R, qname, strlen(qname), qtype, qclass);
} /* dns_res_submit() */
int dns_res_check(struct dns_resolver *R) {
int error;
if (R->stack[0].state != DNS_R_DONE) {
if ((error = dns_res_exec(R)))
return error;
}
return 0;
} /* dns_res_check() */
struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (R->stack[0].state != DNS_R_DONE) {
error = DNS_EUNKNOWN;
goto error;
}
if (!dns_p_movptr(&P, &R->stack[0].answer)) {
error = DNS_EFETCHED;
goto error;
}
dns_trace_res_fetch(R->trace, P, 0);
return P;
error:
*_error = error;
dns_trace_res_fetch(R->trace, NULL, error);
return NULL;
} /* dns_res_fetch() */
static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (!(P = dns_res_fetch(R, &error)))
goto error;
if ((error = dns_p_study(P)))
goto error;
return P;
error:
*_error = error;
dns_p_free(P);
return NULL;
} /* dns_res_fetch_and_study() */
struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) {
int error;
if ((error = dns_res_submit(res, qname, qtype, qclass)))
goto error;
while ((error = dns_res_check(res))) {
if (dns_res_elapsed(res) > timeout)
error = DNS_ETIMEDOUT;
if (error != DNS_EAGAIN)
goto error;
if ((error = dns_res_poll(res, 1)))
goto error;
}
return dns_res_fetch(res, error_);
error:
*error_ = error;
return 0;
} /* dns_res_query() */
const struct dns_stat *dns_res_stat(struct dns_resolver *res) {
return dns_so_stat(&res->so);
} /* dns_res_stat() */
void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) {
dns_hints_acquire(hints); /* acquire first in case same hints object */
dns_hints_close(res->hints);
res->hints = hints;
} /* dns_res_sethints() */
struct dns_trace *dns_res_trace(struct dns_resolver *res) {
return res->trace;
} /* dns_res_trace() */
void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) {
struct dns_trace *otrace = res->trace;
res->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
dns_so_settrace(&res->so, trace);
} /* dns_res_settrace() */
/*
* A D D R I N F O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_addrinfo {
struct addrinfo hints;
struct dns_resolver *res;
struct dns_trace *trace;
char qname[DNS_D_MAXNAME + 1];
enum dns_type qtype;
unsigned short qport, port;
struct {
unsigned long todo;
int state;
int atype;
enum dns_type qtype;
} af;
struct dns_packet *answer;
struct dns_packet *glue;
struct dns_rr_i i, g;
struct dns_rr rr;
char cname[DNS_D_MAXNAME + 1];
char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1];
int g_depth;
int state;
int found;
struct dns_stat st;
}; /* struct dns_addrinfo */
#define DNS_AI_AFMAX 32
#define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1))
static inline unsigned long dns_ai_af2index(int af) {
dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch");
dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch");
return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0;
}
static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) {
ai->af.atype = af;
ai->af.qtype = qtype;
ai->af.todo &= ~dns_ai_af2index(af);
return af;
} /* dns_ai_setaf() */
#define DNS_SM_RESTORE \
do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0)
#define DNS_SM_SAVE \
do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0)
static int dns_ai_nextaf(struct dns_addrinfo *ai) {
int i, pc;
dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0");
dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large");
dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large");
DNS_SM_ENTER;
if (ai->res) {
/*
* NB: On OpenBSD, at least, the types of entries resolved
* is the intersection of the /etc/resolv.conf families and
* the families permitted by the .ai_type hint. So if
* /etc/resolv.conf has "family inet4" and .ai_type
* is AF_INET6, then the address ::1 will return 0 entries
* even if AI_NUMERICHOST is specified in .ai_flags.
*/
while (i < (int)lengthof(ai->res->resconf->family)) {
int af = ai->res->resconf->family[i++];
if (af == AF_UNSPEC) {
DNS_SM_EXIT;
} else if (af < 0 || af > DNS_AI_AFMAX) {
continue;
} else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) {
continue;
} else if (af == AF_INET) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
} else if (af == AF_INET6) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
}
} else {
/*
* NB: If we get here than AI_NUMERICFLAGS should be set and
* order shouldn't matter.
*/
if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
DNS_SM_LEAVE;
return dns_ai_setaf(ai, AF_UNSPEC, 0);
} /* dns_ai_nextaf() */
#undef DNS_SM_RESTORE
#undef DNS_SM_SAVE
static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) {
return (ai->qtype)? ai->qtype : ai->af.qtype;
} /* dns_ai_qtype() */
/* JW: This is not defined on mingw. */
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) {
const char *cp = serv;
unsigned long n = 0;
while (*cp >= '0' && *cp <= '9' && n < 65536) {
n *= 10;
n += *cp++ - '0';
}
if (*cp == '\0') {
if (cp == serv || n >= 65536)
return DNS_ESERVICE;
*port = n;
return 0;
}
if (hints->ai_flags & AI_NUMERICSERV)
return DNS_ESERVICE;
/* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */
return DNS_ESERVICE;
} /* dns_ai_parseport() */
struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) {
static const struct dns_addrinfo ai_initializer;
struct dns_addrinfo *ai;
int error;
if (res) {
dns_res_acquire(res);
} else if (!(hints->ai_flags & AI_NUMERICHOST)) {
/*
* NOTE: it's assumed that *_error is set from a previous
* API function call, such as dns_res_stub(). Should change
* this semantic, but it's applied elsewhere, too.
*/
if (!*_error)
*_error = EINVAL;
return NULL;
}
if (!(ai = malloc(sizeof *ai)))
goto syerr;
*ai = ai_initializer;
ai->hints = *hints;
ai->res = res;
res = NULL;
if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname))
{ error = ENAMETOOLONG; goto error; }
ai->qtype = qtype;
ai->qport = 0;
if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints)))
goto error;
ai->port = ai->qport;
/*
* FIXME: If an explicit A or AAAA record type conflicts with
* .ai_family or with resconf.family (i.e. AAAA specified but
* AF_INET6 not in interection of .ai_family and resconf.family),
* then what?
*/
switch (ai->qtype) {
case DNS_T_A:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case DNS_T_AAAA:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default: /* 0, MX, SRV, etc */
switch (ai->hints.ai_family) {
case AF_UNSPEC:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6);
break;
case AF_INET:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case AF_INET6:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default:
break;
}
}
return ai;
syerr:
error = dns_syerr();
error:
*_error = error;
dns_ai_close(ai);
dns_res_close(res);
return NULL;
} /* dns_ai_open() */
void dns_ai_close(struct dns_addrinfo *ai) {
if (!ai)
return;
dns_res_close(ai->res);
dns_trace_close(ai->trace);
if (ai->answer != ai->glue)
dns_p_free(ai->glue);
dns_p_free(ai->answer);
free(ai);
} /* dns_ai_close() */
static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) {
union u {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr_storage ss;
} addr;
const char *cname;
size_t clen;
switch (type) {
case DNS_T_A:
memset(&addr.sin, '\0', sizeof addr.sin);
addr.sin.sin_family = AF_INET;
addr.sin.sin_port = htons(ai->port);
memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr);
break;
case DNS_T_AAAA:
memset(&addr.sin6, '\0', sizeof addr.sin6);
addr.sin6.sin6_family = AF_INET6;
addr.sin6.sin6_port = htons(ai->port);
memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr);
break;
default:
return EINVAL;
} /* switch() */
if (ai->hints.ai_flags & AI_CANONNAME) {
cname = (*ai->cname)? ai->cname : ai->qname;
clen = strlen(cname);
} else {
cname = NULL;
clen = 0;
}
if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0))))
return dns_syerr();
memset(*ent, '\0', sizeof **ent);
(*ent)->ai_family = addr.ss.ss_family;
(*ent)->ai_socktype = ai->hints.ai_socktype;
(*ent)->ai_protocol = ai->hints.ai_protocol;
(*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr));
(*ent)->ai_addrlen = dns_sa_len(&addr);
if (ai->hints.ai_flags & AI_CANONNAME)
(*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1);
ai->found++;
return 0;
} /* dns_ai_setent() */
enum dns_ai_state {
DNS_AI_S_INIT,
DNS_AI_S_NEXTAF,
DNS_AI_S_NUMERIC,
DNS_AI_S_SUBMIT,
DNS_AI_S_CHECK,
DNS_AI_S_FETCH,
DNS_AI_S_FOREACH_I,
DNS_AI_S_INIT_G,
DNS_AI_S_ITERATE_G,
DNS_AI_S_FOREACH_G,
DNS_AI_S_SUBMIT_G,
DNS_AI_S_CHECK_G,
DNS_AI_S_FETCH_G,
DNS_AI_S_DONE,
}; /* enum dns_ai_state */
#define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0)
int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) {
struct dns_packet *ans, *glue;
struct dns_rr rr;
char qname[DNS_D_MAXNAME + 1];
union dns_any any;
size_t qlen, clen;
int error;
*ent = 0;
exec:
switch (ai->state) {
case DNS_AI_S_INIT:
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NEXTAF:
if (!dns_ai_nextaf(ai))
dns_ai_goto(DNS_AI_S_DONE);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NUMERIC:
if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) {
if (ai->af.atype == AF_INET) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_A, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) {
if (ai->af.atype == AF_INET6) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_AAAA, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (ai->hints.ai_flags & AI_NUMERICHOST)
dns_ai_goto(DNS_AI_S_NEXTAF);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_SUBMIT:
assert(ai->res);
if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_CHECK:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = dns_p_movptr(&ai->answer, &ans);
/* Search generator may have changed the qname. */
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error)))
return error;
else if (qlen >= sizeof qname)
return DNS_EILLEGAL;
if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error))
return error;
dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname);
dns_rr_i_init(&ai->i);
ai->i.section = DNS_S_AN;
ai->i.name = ai->i_cname;
ai->i.type = dns_ai_qtype(ai);
ai->i.sort = &dns_rr_i_order;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_I:
if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error))
dns_ai_goto(DNS_AI_S_NEXTAF);
if ((error = dns_any_parse(&any, &rr, ai->answer)))
return error;
ai->port = ai->qport;
switch (rr.type) {
case DNS_T_A:
case DNS_T_AAAA:
return dns_ai_setent(ent, &any, rr.type, ai);
default:
if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type)))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/*
* Find the "real" canonical name. Some authorities
* publish aliases where an RFC defines a canonical
* name. We trust that the resolver followed any
* CNAME chains on it's own, regardless of whether
* the "smart" option is enabled.
*/
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error))
return error;
if (rr.type == DNS_T_SRV)
ai->port = any.srv.port;
break;
} /* switch() */
ai->state++; /* FALL THROUGH */
case DNS_AI_S_INIT_G:
ai->g_depth = 0;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_ITERATE_G:
dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname);
dns_rr_i_init(&ai->g);
ai->g.section = DNS_S_ALL & ~DNS_S_QD;
ai->g.name = ai->g_cname;
ai->g.type = ai->af.qtype;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_G:
if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) {
if (dns_rr_i_count(&ai->g) > 0)
dns_ai_goto(DNS_AI_S_FOREACH_I);
else
dns_ai_goto(DNS_AI_S_SUBMIT_G);
}
if ((error = dns_any_parse(&any, &rr, ai->glue)))
return error;
return dns_ai_setent(ent, &any, rr.type, ai);
case DNS_AI_S_SUBMIT_G:
{
struct dns_rr_i I_instance = { 0 };
I_instance.section = DNS_S_QD;
I_instance.name = ai->g.name;
I_instance.type = ai->g.type;
/* skip if already queried */
if (dns_rr_grep(&rr, 1, &I_instance, ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/* skip if we recursed (CNAME chains should have been handled in the resolver) */
if (++ai->g_depth > 1)
dns_ai_goto(DNS_AI_S_FOREACH_I);
if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN)))
return error;
ai->state++;
} /* FALL THROUGH */
case DNS_AI_S_CHECK_G:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH_G:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error);
dns_p_setptr(&ans, NULL);
if (!glue)
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = glue;
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
dns_ai_goto(DNS_AI_S_ITERATE_G);
case DNS_AI_S_DONE:
if (ai->found) {
return ENOENT; /* TODO: Just return 0 */
} else if (ai->answer) {
switch (dns_p_rcode(ai->answer)) {
case DNS_RC_NOERROR:
/* FALL THROUGH */
case DNS_RC_NXDOMAIN:
return DNS_ENONAME;
default:
return DNS_EFAIL;
}
} else {
return DNS_EFAIL;
}
default:
return EINVAL;
} /* switch() */
} /* dns_ai_nextent() */
time_t dns_ai_elapsed(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_elapsed(ai->res) : 0;
} /* dns_ai_elapsed() */
void dns_ai_clear(struct dns_addrinfo *ai) {
if (ai->res)
dns_res_clear(ai->res);
} /* dns_ai_clear() */
int dns_ai_events(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_events(ai->res) : 0;
} /* dns_ai_events() */
int dns_ai_pollfd(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_pollfd(ai->res) : -1;
} /* dns_ai_pollfd() */
time_t dns_ai_timeout(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_timeout(ai->res) : 0;
} /* dns_ai_timeout() */
int dns_ai_poll(struct dns_addrinfo *ai, int timeout) {
return (ai->res)? dns_res_poll(ai->res, timeout) : 0;
} /* dns_ai_poll() */
size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
dns_b_puts(&dst, "[ ");
dns_b_puts(&dst, ai->qname);
dns_b_puts(&dst, " IN ");
if (ai->qtype) {
dns_b_puts(&dst, dns_strtype(ai->qtype));
} else if (ent->ai_family == AF_INET) {
dns_b_puts(&dst, dns_strtype(DNS_T_A));
} else if (ent->ai_family == AF_INET6) {
dns_b_puts(&dst, dns_strtype(DNS_T_AAAA));
} else {
dns_b_puts(&dst, "0");
}
dns_b_puts(&dst, " ]\n");
dns_b_puts(&dst, ".ai_family = ");
switch (ent->ai_family) {
case AF_INET:
dns_b_puts(&dst, "AF_INET");
break;
case AF_INET6:
dns_b_puts(&dst, "AF_INET6");
break;
default:
dns_b_fmtju(&dst, ent->ai_family, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_socktype = ");
switch (ent->ai_socktype) {
case SOCK_STREAM:
dns_b_puts(&dst, "SOCK_STREAM");
break;
case SOCK_DGRAM:
dns_b_puts(&dst, "SOCK_DGRAM");
break;
default:
dns_b_fmtju(&dst, ent->ai_socktype, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr);
dns_b_puts(&dst, ".ai_addr = [");
dns_b_puts(&dst, addr);
dns_b_puts(&dst, "]:");
dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0);
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_canonname = ");
dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]");
dns_b_putc(&dst, '\n');
return dns_b_strllen(&dst);
} /* dns_ai_print() */
const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_stat(ai->res) : &ai->st;
} /* dns_ai_stat() */
struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) {
return ai->trace;
} /* dns_ai_trace() */
void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) {
struct dns_trace *otrace = ai->trace;
ai->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
if (ai->res)
dns_res_settrace(ai->res, trace);
} /* dns_ai_settrace() */
/*
* M I S C E L L A N E O U S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static const struct {
char name[16];
enum dns_section type;
} dns_sections[] = {
{ "QUESTION", DNS_S_QUESTION },
{ "QD", DNS_S_QUESTION },
{ "ANSWER", DNS_S_ANSWER },
{ "AN", DNS_S_ANSWER },
{ "AUTHORITY", DNS_S_AUTHORITY },
{ "NS", DNS_S_AUTHORITY },
{ "ADDITIONAL", DNS_S_ADDITIONAL },
{ "AR", DNS_S_ADDITIONAL },
};
const char *(dns_strsection)(enum dns_section section) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_sections); i++) {
if (dns_sections[i].type & section) {
dns_b_puts(&dst, dns_sections[i].name);
section &= ~dns_sections[i].type;
if (section)
dns_b_putc(&dst, '|');
}
}
if (section || dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & section), 0);
return dns_b_tostring(&dst);
} /* dns_strsection() */
enum dns_section dns_isection(const char *src) {
enum dns_section section = 0;
char sbuf[128];
char *name, *next;
unsigned i;
dns_strlcpy(sbuf, src, sizeof sbuf);
next = sbuf;
while ((name = dns_strsep(&next, "|+, \t"))) {
for (i = 0; i < lengthof(dns_sections); i++) {
if (!strcasecmp(dns_sections[i].name, name)) {
section |= dns_sections[i].type;
break;
}
}
}
return section;
} /* dns_isection() */
static const struct {
char name[8];
enum dns_class type;
} dns_classes[] = {
{ "IN", DNS_C_IN },
};
const char *(dns_strclass)(enum dns_class type) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_classes); i++) {
if (dns_classes[i].type == type) {
dns_b_puts(&dst, dns_classes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strclass() */
enum dns_class dns_iclass(const char *name) {
unsigned i, class;
for (i = 0; i < lengthof(dns_classes); i++) {
if (!strcasecmp(dns_classes[i].name, name))
return dns_classes[i].type;
}
class = 0;
while (dns_isdigit(*name)) {
class *= 10;
class += *name++ - '0';
}
return DNS_PP_MIN(class, 0xffff);
} /* dns_iclass() */
const char *(dns_strtype)(enum dns_type type) {
char _dst[DNS_STRMAXLEN + 1] = { 0 };
struct dns_buf dst = DNS_B_INTO(_dst, sizeof _dst);
unsigned i;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (dns_rrtypes[i].type == type) {
dns_b_puts(&dst, dns_rrtypes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strtype() */
enum dns_type dns_itype(const char *name) {
unsigned i, type;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (!strcasecmp(dns_rrtypes[i].name, name))
return dns_rrtypes[i].type;
}
type = 0;
while (dns_isdigit(*name)) {
type *= 10;
type += *name++ - '0';
}
return DNS_PP_MIN(type, 0xffff);
} /* dns_itype() */
static char dns_opcodes[16][16] = {
[DNS_OP_QUERY] = "QUERY",
[DNS_OP_IQUERY] = "IQUERY",
[DNS_OP_STATUS] = "STATUS",
[DNS_OP_NOTIFY] = "NOTIFY",
[DNS_OP_UPDATE] = "UPDATE",
};
static const char *dns__strcode(int code, volatile char *dst, size_t lim) {
char _tmp[48] = "";
struct dns_buf tmp;
size_t p;
assert(lim > 0);
dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0);
/* copy downwards so first byte is copied last (see below) */
p = (size_t)(tmp.p - tmp.base);
dst[p] = '\0';
while (p--)
dst[p] = _tmp[p];
return (const char *)dst;
}
const char *dns_stropcode(enum dns_opcode opcode) {
opcode = (unsigned)opcode % lengthof(dns_opcodes);
if ('\0' == dns_opcodes[opcode][0])
return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]);
return dns_opcodes[opcode];
} /* dns_stropcode() */
enum dns_opcode dns_iopcode(const char *name) {
unsigned opcode;
for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) {
if (!strcasecmp(name, dns_opcodes[opcode]))
return opcode;
}
opcode = 0;
while (dns_isdigit(*name)) {
opcode *= 10;
opcode += *name++ - '0';
}
return DNS_PP_MIN(opcode, 0x0f);
} /* dns_iopcode() */
static char dns_rcodes[32][16] = {
[DNS_RC_NOERROR] = "NOERROR",
[DNS_RC_FORMERR] = "FORMERR",
[DNS_RC_SERVFAIL] = "SERVFAIL",
[DNS_RC_NXDOMAIN] = "NXDOMAIN",
[DNS_RC_NOTIMP] = "NOTIMP",
[DNS_RC_REFUSED] = "REFUSED",
[DNS_RC_YXDOMAIN] = "YXDOMAIN",
[DNS_RC_YXRRSET] = "YXRRSET",
[DNS_RC_NXRRSET] = "NXRRSET",
[DNS_RC_NOTAUTH] = "NOTAUTH",
[DNS_RC_NOTZONE] = "NOTZONE",
/* EDNS(0) extended RCODEs ... */
[DNS_RC_BADVERS] = "BADVERS",
};
const char *dns_strrcode(enum dns_rcode rcode) {
rcode = (unsigned)rcode % lengthof(dns_rcodes);
if ('\0' == dns_rcodes[rcode][0])
return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]);
return dns_rcodes[rcode];
} /* dns_strrcode() */
enum dns_rcode dns_ircode(const char *name) {
unsigned rcode;
for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) {
if (!strcasecmp(name, dns_rcodes[rcode]))
return rcode;
}
rcode = 0;
while (dns_isdigit(*name)) {
rcode *= 10;
rcode += *name++ - '0';
}
return DNS_PP_MIN(rcode, 0xfff);
} /* dns_ircode() */
/*
* C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if DNS_MAIN
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#if _WIN32
#include <getopt.h>
#endif
#if !_WIN32
#include <err.h>
#endif
struct {
struct {
const char *path[8];
unsigned count;
} resconf, nssconf, hosts, cache;
const char *qname;
enum dns_type qtype;
int (*sort)();
const char *trace;
int verbose;
struct {
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_trace *trace;
} memo;
struct sockaddr_storage socks_host;
const char *socks_user;
const char *socks_password;
} MAIN = {
.sort = &dns_rr_i_packet,
};
static void hexdump(const unsigned char *src, size_t len, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
while (dns_hxd_lines(line, sizeof line, src, len, &lines)) {
fputs(line, fp);
}
} /* hexdump() */
DNS_NORETURN static void panic(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
#if _WIN32
vfprintf(stderr, fmt, ap);
exit(EXIT_FAILURE);
#else
verrx(EXIT_FAILURE, fmt, ap);
#endif
} /* panic() */
#define panic_(fn, ln, fmt, ...) \
panic(fmt "%0s", (fn), (ln), __VA_ARGS__)
#define panic(...) \
panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "")
static void *grow(unsigned char *p, size_t size) {
void *tmp;
if (!(tmp = realloc(p, size)))
panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno));
return tmp;
} /* grow() */
static size_t add(size_t a, size_t b) {
if (~a < b)
panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b);
return a + b;
} /* add() */
static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) {
size_t size = add(osize, len);
*dst = grow(*dst, size);
memcpy(*dst + osize, src, len);
return size;
} /* append() */
static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) {
size_t size = osize;
unsigned char buf[1024];
size_t count;
while ((count = fread(buf, 1, sizeof buf, fp)))
size = append(dst, size, buf, count);
if (ferror(fp))
panic("%s: %s", path, dns_strerror(errno));
return size;
} /* slurp() */
static struct dns_resolv_conf *resconf(void) {
struct dns_resolv_conf **resconf = &MAIN.memo.resconf;
const char *path;
unsigned i;
int error;
if (*resconf)
return *resconf;
if (!(*resconf = dns_resconf_open(&error)))
panic("dns_resconf_open: %s", dns_strerror(error));
if (!MAIN.resconf.count)
MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf";
for (i = 0; i < MAIN.resconf.count; i++) {
path = MAIN.resconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_resconf_loadfile(*resconf, stdin);
else
error = dns_resconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
for (i = 0; i < MAIN.nssconf.count; i++) {
path = MAIN.nssconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_nssconf_loadfile(*resconf, stdin);
else
error = dns_nssconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
if (!MAIN.nssconf.count) {
path = "/etc/nsswitch.conf";
if (!(error = dns_nssconf_loadpath(*resconf, path)))
MAIN.nssconf.path[MAIN.nssconf.count++] = path;
else if (error != ENOENT)
panic("%s: %s", path, dns_strerror(error));
}
return *resconf;
} /* resconf() */
static struct dns_hosts *hosts(void) {
struct dns_hosts **hosts = &MAIN.memo.hosts;
const char *path;
unsigned i;
int error;
if (*hosts)
return *hosts;
if (!MAIN.hosts.count) {
MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts";
/* Explicitly test dns_hosts_local() */
if (!(*hosts = dns_hosts_local(&error)))
panic("%s: %s", "/etc/hosts", dns_strerror(error));
return *hosts;
}
if (!(*hosts = dns_hosts_open(&error)))
panic("dns_hosts_open: %s", dns_strerror(error));
for (i = 0; i < MAIN.hosts.count; i++) {
path = MAIN.hosts.path[i];
if (0 == strcmp(path, "-"))
error = dns_hosts_loadfile(*hosts, stdin);
else
error = dns_hosts_loadpath(*hosts, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
return *hosts;
} /* hosts() */
#if DNS_CACHE
#include "cache.h"
static struct dns_cache *cache(void) {
static struct cache *cache;
const char *path;
unsigned i;
int error;
if (cache)
return cache_resi(cache);
if (!MAIN.cache.count)
return NULL;
if (!(cache = cache_open(&error)))
panic("%s: %s", MAIN.cache.path[0], dns_strerror(error));
for (i = 0; i < MAIN.cache.count; i++) {
path = MAIN.cache.path[i];
if (!strcmp(path, "-")) {
if ((error = cache_loadfile(cache, stdin, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
} else if ((error = cache_loadpath(cache, path, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
}
return cache_resi(cache);
} /* cache() */
#else
static struct dns_cache *cache(void) { return NULL; }
#endif
static struct dns_trace *trace(const char *mode) {
static char omode[64] = "";
struct dns_trace **trace = &MAIN.memo.trace;
FILE *fp;
int error;
if (*trace && 0 == strcmp(omode, mode))
return *trace;
if (!MAIN.trace)
return NULL;
if (!(fp = fopen(MAIN.trace, mode)))
panic("%s: %s", MAIN.trace, strerror(errno));
dns_trace_close(*trace);
if (!(*trace = dns_trace_open(fp, &error)))
panic("%s: %s", MAIN.trace, dns_strerror(error));
dns_strlcpy(omode, mode, sizeof omode);
return *trace;
}
static void print_packet(struct dns_packet *P, FILE *fp) {
struct dns_rr_i I_instance = { 0 };
I.sort = MAIN.sort;
dns_p_dump3(P, &I, fp);
if (MAIN.verbose > 2)
hexdump(P->data, P->end, fp);
} /* print_packet() */
static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
enum dns_section section;
struct dns_rr rr;
int error;
union dns_any any;
char pretty[sizeof any * 2];
size_t len;
P->end = fread(P->data, 1, P->size, stdin);
fputs(";; [HEADER]\n", stdout);
fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
section = 0;
dns_rr_foreach(&rr, P, .sort = MAIN.sort) {
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error)))
fprintf(stdout, "%s\n", pretty);
dns_rr_copy(Q, &rr, P);
section = rr.section;
}
fputs("; ; ; ; ; ; ; ;\n\n", stdout);
section = 0;
#if 0
dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") {
#else
char _p[DNS_D_MAXNAME + 1] = { 0 };
const char *dn = "ns8.yahoo.com";
char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
struct dns_rr rrset[32];
struct dns_rr_i I_instance = { 0 };
struct dns_rr_i *rri = &I;
unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error);
I.name = _name;
I.sort = MAIN.sort;
for (unsigned i = 0; i < rrcount; i++) {
rr = rrset[i];
#endif
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error)))
fprintf(stdout, "%s\n", pretty);
section = rr.section;
}
if (MAIN.verbose > 1) {
fprintf(stderr, "orig:%"PRIuZ"\n", P->end);
hexdump(P->data, P->end, stdout);
fprintf(stderr, "copy:%"PRIuZ"\n", Q->end);
hexdump(Q->data, Q->end, stdout);
}
return 0;
} /* parse_packet() */
static int parse_domain(int argc, char *argv[]) {
char _p[DNS_D_MAXNAME + 1] = { 0 };
char *dn;
dn = (argc > 1)? argv[1] : "f.l.google.com";
printf("[%s]\n", dn);
dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
do {
puts(dn);
} while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn)));
return 0;
} /* parse_domain() */
static int trim_domain(int argc, char **argv) {
for (argc--, argv++; argc > 0; argc--, argv++) {
char name[DNS_D_MAXNAME + 1];
dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR);
puts(name);
}
return 0;
} /* trim_domain() */
static int expand_domain(int argc, char *argv[]) {
unsigned short rp = 0;
unsigned char *src = NULL;
unsigned char *dst;
struct dns_packet *pkt;
size_t lim = 0, len;
int error;
if (argc > 1)
rp = atoi(argv[1]);
len = slurp(&src, 0, stdin, "-");
if (!(pkt = dns_p_make(len, &error)))
panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error));
memcpy(pkt->data, src, len);
pkt->end = len;
lim = 1;
dst = grow(NULL, lim);
while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) {
lim = add(len, 1);
dst = grow(dst, lim);
}
if (!len)
panic("expand: %s", dns_strerror(error));
fwrite(dst, 1, len, stdout);
fflush(stdout);
free(src);
free(dst);
free(pkt);
return 0;
} /* expand_domain() */
static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf(); /* load it */
fputs("; SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]);
fputs(";\n", stdout);
dns_resconf_dump(resconf(), stdout);
return 0;
} /* show_resconf() */
static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]);
fputs("#\n", stdout);
dns_nssconf_dump(resconf(), stdout);
return 0;
} /* show_nssconf() */
static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
hosts();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.hosts.count; i++)
fprintf(stdout, "# %s\n", MAIN.hosts.path[i]);
fputs("#\n", stdout);
dns_hosts_dump(hosts(), stdout);
return 0;
} /* show_hosts() */
static int query_hosts(int argc, char *argv[]) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
struct dns_packet *A;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
int error;
if (!MAIN.qname)
MAIN.qname = (argc > 1)? argv[1] : "localhost";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
hosts();
if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) {
union { struct in_addr a; struct in6_addr a6; } addr;
int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(af, MAIN.qname, &addr)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
qlen = dns_ptr_qname(qname, sizeof qname, af, &addr);
} else
qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname);
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0)))
panic("%s: %s", qname, dns_strerror(error));
if (!(A = dns_hosts_query(hosts(), Q, &error)))
panic("%s: %s", qname, dns_strerror(error));
print_packet(A, stdout);
free(A);
return 0;
} /* query_hosts() */
static int search_list(int argc, char *argv[]) {
const char *qname = (argc > 1)? argv[1] : "f.l.google.com";
unsigned long i = 0;
char name[DNS_D_MAXNAME + 1];
printf("[%s]\n", qname);
while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i))
puts(name);
return 0;
} /* search_list() */
static int permute_set(int argc, char *argv[]) {
unsigned lo, hi, i;
struct dns_k_permutor p;
hi = (--argc > 0)? atoi(argv[argc]) : 8;
lo = (--argc > 0)? atoi(argv[argc]) : 0;
fprintf(stderr, "[%u .. %u]\n", lo, hi);
dns_k_permutor_init(&p, lo, hi);
for (i = lo; i <= hi; i++)
fprintf(stdout, "%u\n", dns_k_permutor_step(&p));
// printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i)));
return 0;
} /* permute_set() */
static int shuffle_16(int argc, char *argv[]) {
unsigned n, r;
if (--argc > 0) {
n = 0xffff & atoi(argv[argc]);
r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random();
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
} else {
r = dns_random();
for (n = 0; n < 65536; n++)
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
}
return 0;
} /* shuffle_16() */
static int dump_random(int argc, char *argv[]) {
unsigned char b[32];
unsigned i, j, n, r;
n = (argc > 1)? atoi(argv[1]) : 32;
while (n) {
i = 0;
do {
r = dns_random();
for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) {
b[i] = 0xff & r;
r >>= 8;
}
} while (i < n && i < sizeof b);
hexdump(b, i, stdout);
n -= i;
}
return 0;
} /* dump_random() */
static int send_query(int argc, char *argv[]) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *A, *Q = dns_p_init(&Q_instance.p, 512);
char host[INET6_ADDRSTRLEN + 1];
struct sockaddr_storage ss;
struct dns_socket *so;
int error, type;
struct dns_options opts = { 0 };
memset(&ss, 0, sizeof ss);
if (argc > 1) {
ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL))))
panic("%s: %s", argv[1], dns_strerror(error));
*dns_sa_port(ss.ss_family, &ss) = htons(53);
} else
memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0]));
if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host))
panic("bad host address, or none provided");
if (!MAIN.qname)
MAIN.qname = "ipv6.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_AAAA;
if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0)))
panic("dns_p_push: %s", dns_strerror(error));
dns_header(Q)->rd = 1;
if (strstr(argv[0], "udp"))
type = SOCK_DGRAM;
else if (strstr(argv[0], "tcp"))
type = SOCK_STREAM;
else
type = dns_res_tcp2type(resconf()->options.tcp);
fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype));
if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error)))
panic("dns_so_open: %s", dns_strerror(error));
while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) {
if (error != DNS_EAGAIN)
panic("dns_so_query: %s (%d)", dns_strerror(error), error);
if (dns_so_elapsed(so) > 10)
panic("query timed-out");
dns_so_poll(so, 1);
}
print_packet(A, stdout);
dns_so_close(so);
return 0;
} /* send_query() */
static int print_arpa(int argc, char *argv[]) {
const char *ip = (argc > 1)? argv[1] : "::1";
int af = (strchr(ip, ':'))? AF_INET6 : AF_INET;
union { struct in_addr a4; struct in6_addr a6; } addr;
char host[DNS_D_MAXNAME + 1];
if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr))
panic("%s: invalid address", ip);
fprintf(stdout, "%s\n", host);
return 0;
} /* print_arpa() */
static int show_hints(int argc, char *argv[]) {
struct dns_hints *(*load)(struct dns_resolv_conf *, int *);
const char *which, *how, *who;
struct dns_hints *hints;
int error;
which = (argc > 1)? argv[1] : "local";
how = (argc > 2)? argv[2] : "plain";
who = (argc > 3)? argv[3] : "google.com";
load = (0 == strcmp(which, "local"))
? &dns_hints_local
: &dns_hints_root;
if (!(hints = load(resconf(), &error)))
panic("%s: %s", argv[0], dns_strerror(error));
if (0 == strcmp(how, "plain")) {
dns_hints_dump(hints, stdout);
} else {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *query, *answer;
query = dns_p_init(&P_instance.p, 512);
if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0)))
panic("%s: %s", who, dns_strerror(error));
if (!(answer = dns_hints_query(hints, query, &error)))
panic("%s: %s", who, dns_strerror(error));
print_packet(answer, stdout);
free(answer);
}
dns_hints_close(hints);
return 0;
} /* show_hints() */
static int resolve_query(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *R;
struct dns_packet *ans;
const struct dns_stat *st;
int error;
struct dns_options opts = { 0 };
opts.socks_host = &MAIN.socks_host;
opts.socks_user = MAIN.socks_user;
opts.socks_password = MAIN.socks_password;
if (!MAIN.qname)
MAIN.qname = "www.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
resconf()->options.recurse = recurse;
if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(),
&opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_res_settrace(R, trace("w+b"));
if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
while ((error = dns_res_check(R))) {
if (error != DNS_EAGAIN)
panic("dns_res_check: %s (%d)", dns_strerror(error), error);
if (dns_res_elapsed(R) > 30)
panic("query timed-out");
dns_res_poll(R, 1);
}
ans = dns_res_fetch(R, &error);
print_packet(ans, stdout);
free(ans);
st = dns_res_stat(R);
putchar('\n');
printf(";; queries: %"PRIuZ"\n", st->queries);
printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes);
printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes);
printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes);
printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes);
dns_res_close(R);
return 0;
} /* resolve_query() */
static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *res = NULL;
struct dns_addrinfo *ai = NULL;
struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME };
struct addrinfo *ent;
char pretty[512];
int error;
struct dns_options opts = { 0 };
if (!MAIN.qname)
MAIN.qname = "www.google.com";
/* NB: MAIN.qtype of 0 means obey hints.ai_family */
resconf()->options.recurse = recurse;
if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_ai_settrace(ai, trace("w+b"));
do {
switch (error = dns_ai_nextent(&ent, ai)) {
case 0:
dns_ai_print(pretty, sizeof pretty, ent, ai);
fputs(pretty, stdout);
free(ent);
break;
case ENOENT:
break;
case DNS_EAGAIN:
if (dns_ai_elapsed(ai) > 30)
panic("query timed-out");
dns_ai_poll(ai, 1);
break;
default:
panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error);
}
} while (error != ENOENT);
dns_res_close(res);
dns_ai_close(ai);
return 0;
} /* resolve_addrinfo() */
static int dump_trace(int argc DNS_NOTUSED, char *argv[]) {
int error;
if (!MAIN.trace)
panic("no trace file specified");
if ((error = dns_trace_dump(trace("r"), stdout)))
panic("dump_trace: %s", dns_strerror(error));
return 0;
} /* dump_trace() */
static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
union {
struct sockaddr sa;
struct sockaddr_in sin;
} port;
int fd;
memset(&port, 0, sizeof port);
port.sin.sin_family = AF_INET;
port.sin.sin_port = htons(5354);
port.sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0)))
panic("socket: %s", strerror(errno));
if (0 != bind(fd, &port.sa, sizeof port.sa))
panic("127.0.0.1:5353: %s", dns_strerror(errno));
for (;;) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *pkt = dns_p_init(&P_instance.p, 512);
struct sockaddr_storage ss;
socklen_t slen = sizeof ss;
ssize_t count;
#if defined(MSG_WAITALL) /* MinGW issue */
int rflags = MSG_WAITALL;
#else
int rflags = 0;
#endif
count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen);
if (!count || count < 0)
panic("recvfrom: %s", strerror(errno));
pkt->end = count;
dns_p_dump(pkt, stdout);
(void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen);
}
return 0;
} /* echo_port() */
static int isection(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_isection(name);
name = dns_strsection(type);
printf("%s (%d)\n", name, type);
return 0;
} /* isection() */
static int iclass(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iclass(name);
name = dns_strclass(type);
printf("%s (%d)\n", name, type);
return 0;
} /* iclass() */
static int itype(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_itype(name);
name = dns_strtype(type);
printf("%s (%d)\n", name, type);
return 0;
} /* itype() */
static int iopcode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iopcode(name);
name = dns_stropcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* iopcode() */
static int ircode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_ircode(name);
name = dns_strrcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* ircode() */
#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) }
#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__)
#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__)
#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__)
#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
static const struct { const char *name; size_t size; } type[] = {
SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i),
SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns),
SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv),
SIZE(struct dns_sshfp, struct dns_txt, union dns_any),
SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i),
SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo),
SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long)
};
unsigned i, max;
for (i = 0, max = 0; i < lengthof(type); i++)
max = DNS_PP_MAX(max, strlen(type[i].name));
for (i = 0; i < lengthof(type); i++)
printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size);
return 0;
} /* sizes() */
static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = {
{ "parse-packet", &parse_packet, "parse binary packet from stdin" },
{ "parse-domain", &parse_domain, "anchor and iteratively cleave domain" },
{ "trim-domain", &trim_domain, "trim and anchor domain name" },
{ "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" },
{ "show-resconf", &show_resconf, "show resolv.conf data" },
{ "show-hosts", &show_hosts, "show hosts data" },
{ "show-nssconf", &show_nssconf, "show nsswitch.conf data" },
{ "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" },
{ "search-list", &search_list, "generate query search list from domain" },
{ "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" },
{ "shuffle-16", &shuffle_16, "simple 16-bit permutation" },
{ "dump-random", &dump_random, "generate random bytes" },
{ "send-query", &send_query, "send query to host" },
{ "send-query-udp", &send_query, "send udp query to host" },
{ "send-query-tcp", &send_query, "send tcp query to host" },
{ "print-arpa", &print_arpa, "print arpa. zone name of address" },
{ "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" },
{ "resolve-stub", &resolve_query, "resolve as stub resolver" },
{ "resolve-recurse", &resolve_query, "resolve as recursive resolver" },
{ "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" },
{ "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" },
/* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */
{ "dump-trace", &dump_trace, "dump the contents of a trace file" },
{ "echo", &echo_port, "server echo mode, for nmap fuzzing" },
{ "isection", &isection, "parse section string" },
{ "iclass", &iclass, "parse class string" },
{ "itype", &itype, "parse type string" },
{ "iopcode", &iopcode, "parse opcode string" },
{ "ircode", &ircode, "parse rcode string" },
{ "sizes", &sizes, "print data structure sizes" },
};
static void print_usage(const char *progname, FILE *fp) {
static const char *usage =
" [OPTIONS] COMMAND [ARGS]\n"
" -c PATH Path to resolv.conf\n"
" -n PATH Path to nsswitch.conf\n"
" -l PATH Path to local hosts\n"
" -z PATH Path to zone cache\n"
" -q QNAME Query name\n"
" -t QTYPE Query type\n"
" -s HOW Sort records\n"
" -S ADDR Address of SOCKS server to use\n"
" -P PORT Port of SOCKS server to use\n"
" -A USER:PASSWORD Credentials for the SOCKS server\n"
" -f PATH Path to trace file\n"
" -v Be more verbose (-vv show packets; -vvv hexdump packets)\n"
" -V Print version info\n"
" -h Print this usage message\n"
"\n";
unsigned i, n, m;
fputs(progname, fp);
fputs(usage, fp);
for (i = 0, m = 0; i < lengthof(cmds); i++) {
if (strlen(cmds[i].cmd) > m)
m = strlen(cmds[i].cmd);
}
for (i = 0; i < lengthof(cmds); i++) {
fprintf(fp, " %s ", cmds[i].cmd);
for (n = strlen(cmds[i].cmd); n < m; n++)
putc(' ', fp);
fputs(cmds[i].help, fp);
putc('\n', fp);
}
fputs("\nReport bugs to William Ahern <william@25thandClement.com>\n", fp);
} /* print_usage() */
static void print_version(const char *progname, FILE *fp) {
fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel());
fprintf(fp, "vendor %s\n", dns_vendor());
fprintf(fp, "release %.8X\n", dns_v_rel());
fprintf(fp, "abi %.8X\n", dns_v_abi());
fprintf(fp, "api %.8X\n", dns_v_api());
} /* print_version() */
static void main_exit(void) {
dns_trace_close(MAIN.memo.trace);
MAIN.memo.trace = NULL;
dns_hosts_close(MAIN.memo.hosts);
MAIN.memo.hosts = NULL;
dns_resconf_close(MAIN.memo.resconf);
MAIN.memo.resconf = NULL;
} /* main_exit() */
int main(int argc, char **argv) {
extern int optind;
extern char *optarg;
const char *progname = argv[0];
unsigned i;
int ch;
atexit(&main_exit);
while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) {
switch (ch) {
case 'c':
assert(MAIN.resconf.count < lengthof(MAIN.resconf.path));
MAIN.resconf.path[MAIN.resconf.count++] = optarg;
break;
case 'n':
assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path));
MAIN.nssconf.path[MAIN.nssconf.count++] = optarg;
break;
case 'l':
assert(MAIN.hosts.count < lengthof(MAIN.hosts.path));
MAIN.hosts.path[MAIN.hosts.count++] = optarg;
break;
case 'z':
assert(MAIN.cache.count < lengthof(MAIN.cache.path));
MAIN.cache.path[MAIN.cache.count++] = optarg;
break;
case 'q':
MAIN.qname = optarg;
break;
case 't':
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (0 == strcasecmp(dns_rrtypes[i].name, optarg))
{ MAIN.qtype = dns_rrtypes[i].type; break; }
}
if (MAIN.qtype)
break;
for (i = 0; dns_isdigit(optarg[i]); i++) {
MAIN.qtype *= 10;
MAIN.qtype += optarg[i] - '0';
}
if (!MAIN.qtype)
panic("%s: invalid query type", optarg);
break;
case 's':
if (0 == strcasecmp(optarg, "packet"))
MAIN.sort = &dns_rr_i_packet;
else if (0 == strcasecmp(optarg, "shuffle"))
MAIN.sort = &dns_rr_i_shuffle;
else if (0 == strcasecmp(optarg, "order"))
MAIN.sort = &dns_rr_i_order;
else
panic("%s: invalid sort method", optarg);
break;
case 'S': {
dns_error_t error;
struct dns_resolv_conf *conf = resconf();
conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET;
if ((error = dns_pton(MAIN.socks_host.ss_family, optarg,
dns_sa_addr(MAIN.socks_host.ss_family,
&MAIN.socks_host, NULL))))
panic("%s: %s", optarg, dns_strerror(error));
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080);
break;
}
case 'P':
if (! MAIN.socks_host.ss_family)
panic("-P without prior -S");
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg));
break;
case 'A': {
char *password;
if (! MAIN.socks_host.ss_family)
panic("-A without prior -S");
if (! (password = strchr(optarg, ':')))
panic("Usage: -A USER:PASSWORD");
*password = 0;
password += 1;
MAIN.socks_user = optarg;
MAIN.socks_password = password;
break;
}
case 'f':
MAIN.trace = optarg;
break;
case 'v':
dns_debug = ++MAIN.verbose;
break;
case 'V':
print_version(progname, stdout);
return 0;
case 'h':
print_usage(progname, stdout);
return 0;
default:
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* switch() */
} /* while() */
argc -= optind;
argv += optind;
for (i = 0; i < lengthof(cmds) && argv[0]; i++) {
if (0 == strcmp(cmds[i].cmd, argv[0]))
return cmds[i].run(argc, argv);
}
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* main() */
#endif /* DNS_MAIN */
/*
* pop file-scoped compiler annotations
*/
#if __clang__
#pragma clang diagnostic pop
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic pop
#endif
diff --git a/dirmngr/http.c b/dirmngr/http.c
index de62edc08..f26675f9b 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1,3761 +1,3761 @@
/* http.c - HTTP protocol handler
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2015-2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Simple HTTP client implementation. We try to keep the code as
self-contained as possible. There are some constraints however:
- estream is required. We now require estream because it provides a
very useful and portable asprintf implementation and the fopencookie
function.
- stpcpy is required
- fixme: list other requirements.
- - Either HTTP_USE_NTBTLS or HTTP_USE_GNUTLS must be defind to select
+ - Either HTTP_USE_NTBTLS or HTTP_USE_GNUTLS must be defined to select
which TLS library to use.
- With HTTP_NO_WSASTARTUP the socket initialization is not done
under Windows. This is useful if the socket layer has already
been initialized elsewhere. This also avoids the installation of
an exit handler to cleanup the socket layer.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <time.h>
# include <fcntl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include <npth.h>
#endif
#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
#endif
#ifdef HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif /*HTTP_USE_GNUTLS*/
#include <assuan.h> /* We need the socket wrapper. */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "dns-stuff.h"
#include "dirmngr-status.h" /* (dirmngr_status_printf) */
#include "http.h"
#include "http-common.h"
#ifdef USE_NPTH
# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) npth_accept ((a), (b), (c))
#else
# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) accept ((a), (b), (c))
#endif
#ifdef HAVE_W32_SYSTEM
#define sock_close(a) closesocket(a)
#else
#define sock_close(a) close(a)
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef INADDR_NONE /* Slowaris is missing that. */
#define INADDR_NONE ((unsigned long)(-1))
#endif /*INADDR_NONE*/
#define HTTP_PROXY_ENV "http_proxy"
#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890@" \
"!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
#if HTTP_USE_NTBTLS
typedef ntbtls_t tls_session_t;
#elif HTTP_USE_GNUTLS
typedef gnutls_session_t tls_session_t;
#else
# error building without TLS is not supported
#endif
static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls);
static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls);
static int remove_escapes (char *string);
static int insert_escapes (char *buffer, const char *string,
const char *special);
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (ctrl_t ctrl, http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag, unsigned int timeout,
strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (ctrl_t ctrl,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
unsigned int timeout, assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length);
static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
static gpgrt_ssize_t cookie_write (void *cookie,
const void *buffer, size_t size);
static int cookie_close (void *cookie);
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t simple_cookie_read (void *cookie,
void *buffer, size_t size);
static gpgrt_ssize_t simple_cookie_write (void *cookie,
const void *buffer, size_t size);
#endif
/* A socket object used to a allow ref counting of sockets. */
struct my_socket_s
{
assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */
int refcount; /* Number of references to this socket. */
};
typedef struct my_socket_s *my_socket_t;
/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
cookie_write,
NULL,
cookie_close
};
struct cookie_s
{
/* Socket object or NULL if already closed. */
my_socket_t sock;
/* The session object or NULL if not used. */
http_session_t session;
/* True if TLS is to be used. */
int use_tls;
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
};
typedef struct cookie_s *cookie_t;
/* Simple cookie functions. Here the cookie is an int with the
* socket. */
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static es_cookie_io_functions_t simple_cookie_functions =
{
simple_cookie_read,
simple_cookie_write,
NULL,
NULL
};
#endif
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
#else
# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */
#endif
/* The session object. */
struct http_session_s
{
unsigned long magic;
int refcount; /* Number of references to this object. */
tls_session_t tls_session;
struct {
int done; /* Verifciation has been done. */
int rc; /* TLS verification return code. */
unsigned int status; /* Verification status. */
} verify;
char *servername; /* Malloced server name. */
/* A callback function to log details of TLS certifciates. */
void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
const void **, size_t *);
/* The flags passed to the session object. */
unsigned int flags;
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
/* The connect timeout */
unsigned int connect_timeout;
#ifdef HTTP_USE_GNUTLS
gnutls_certificate_credentials_t certcred;
#endif /*HTTP_USE_GNUTLS*/
};
/* An object to save header lines. */
struct header_s
{
struct header_s *next;
char *value; /* The value of the header (malloced). */
char name[1]; /* The name of the header (canonicalized). */
};
typedef struct header_s *header_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
#else
# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */
#endif
/* Our handle context. */
struct http_context_s
{
unsigned long magic;
unsigned int status_code;
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
void *read_cookie;
http_session_t session;
parsed_uri_t uri;
http_req_t req_type;
char *buffer; /* Line buffer. */
size_t buffer_size;
unsigned int flags;
header_t headers; /* Received headers. */
};
/* Two flags to enable verbose and debug mode. Although currently not
* set-able a value > 1 for OPT_DEBUG enables debugging of the session
* reference counting. */
static int opt_verbose;
static int opt_debug;
/* The global callback for the verification function. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
/* The list of files with trusted CA certificates. */
static strlist_t tls_ca_certlist;
/* The list of files with extra trusted CA certificates. */
static strlist_t cfg_ca_certlist;
/* The global callback for net activity. */
static void (*netactivity_cb)(void);
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
#if GNUPG_MAJOR_VERSION == 1
#define REQ_WINSOCK_MAJOR 1
#define REQ_WINSOCK_MINOR 1
#else
#define REQ_WINSOCK_MAJOR 2
#define REQ_WINSOCK_MINOR 2
#endif
static void
deinit_sockets (void)
{
WSACleanup();
}
static void
init_sockets (void)
{
static int initialized;
static WSADATA wsdata;
if (initialized)
return;
if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
{
log_error ("error initializing socket library: ec=%d\n",
(int)WSAGetLastError () );
return;
}
if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
|| HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
{
log_error ("socket library version is %x.%x - but %d.%d needed\n",
LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
WSACleanup();
return;
}
atexit ( deinit_sockets );
initialized = 1;
}
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
/* Create a new socket object. Returns NULL and closes FD if not
enough memory is available. */
static my_socket_t
_my_socket_new (int lnr, assuan_fd_t fd)
{
my_socket_t so;
so = xtrymalloc (sizeof *so);
if (!so)
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return NULL;
}
so->fd = fd;
so->refcount = 1;
if (opt_debug)
log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
lnr, so, (int)so->fd);
return so;
}
#define my_socket_new(a) _my_socket_new (__LINE__, (a))
/* Bump up the reference counter for the socket object SO. */
static my_socket_t
_my_socket_ref (int lnr, my_socket_t so)
{
so->refcount++;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
lnr, so, (int)so->fd, so->refcount);
return so;
}
#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
/* Bump down the reference counter for the socket object SO. If SO
has no more references, close the socket and release the
object. */
static void
_my_socket_unref (int lnr, my_socket_t so,
void (*preclose)(void*), void *preclosearg)
{
if (so)
{
so->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
lnr, so, (int)so->fd, so->refcount);
if (!so->refcount)
{
if (preclose)
preclose (preclosearg);
assuan_sock_close (so->fd);
xfree (so);
}
}
}
#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
#ifdef HTTP_USE_GNUTLS
static ssize_t
my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_read (sock->fd, buffer, size);
#else
return read (sock->fd, buffer, size);
#endif
}
static ssize_t
my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
{
my_socket_t sock = ptr;
#if USE_NPTH
return npth_write (sock->fd, buffer, size);
#else
return write (sock->fd, buffer, size);
#endif
}
#endif /*HTTP_USE_GNUTLS*/
#ifdef HTTP_USE_NTBTLS
/* Connect the ntbls callback to our generic callback. */
static gpg_error_t
my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
{
http_t hd = opaque;
(void)verify_flags;
log_assert (hd && hd->session && hd->session->verify_cb);
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
return hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
tls);
}
#endif /*HTTP_USE_NTBTLS*/
/* This notification function is called by estream whenever stream is
closed. Its purpose is to mark the closing in the handle so
that a http_close won't accidentally close the estream. The function
http_close removes this notification so that it won't be called if
http_close was used before an es_fclose. */
static void
fp_onclose_notification (estream_t stream, void *opaque)
{
http_t hd = opaque;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
if (hd->fp_read && hd->fp_read == stream)
hd->fp_read = NULL;
else if (hd->fp_write && hd->fp_write == stream)
hd->fp_write = NULL;
}
/*
* Helper function to create an HTTP header with hex encoded data. A
* new buffer is returned. This buffer is the concatenation of the
* string PREFIX, the hex-encoded DATA of length LEN and the string
* SUFFIX. On error NULL is returned and ERRNO set.
*/
static char *
make_header_line (const char *prefix, const char *suffix,
const void *data, size_t len )
{
static unsigned char bintoasc[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const unsigned char *s = data;
char *buffer, *p;
buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
if (!buffer)
return NULL;
p = stpcpy (buffer, prefix);
for ( ; len >= 3 ; len -= 3, s += 3 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
*p++ = bintoasc[s[2]&077];
*p = 0;
}
if ( len == 2 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[((s[1]<<2)&074)];
*p++ = '=';
}
else if ( len == 1 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(s[0] <<4)&060];
*p++ = '=';
*p++ = '=';
}
*p = 0;
strcpy (p, suffix);
return buffer;
}
/* Set verbosity and debug mode for this module. */
void
http_set_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
always returns NULL. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
tls_callback = cb;
}
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
removed. */
void
http_register_tls_ca (const char *fname)
{
strlist_t sl;
if (!fname)
{
free_strlist (tls_ca_certlist);
tls_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if (access (fname, F_OK))
log_info (_("can't access '%s': %s\n"), fname,
gpg_strerror (gpg_error_from_syserror()));
sl = add_to_strlist (&tls_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a CA certificate for future use. The certificate is
* expected to be in FNAME. PEM format is assume if FNAME has a
* suffix of ".pem". If FNAME is NULL the list of CA files is
* removed. This is a variant of http_register_tls_ca which puts the
* certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */
void
http_register_cfg_ca (const char *fname)
{
strlist_t sl;
if (!fname)
{
free_strlist (cfg_ca_certlist);
cfg_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if (access (fname, F_OK))
log_info (_("can't access '%s': %s\n"), fname,
gpg_strerror (gpg_error_from_syserror()));
sl = add_to_strlist (&cfg_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a callback which is called every time the HTTP mode has
* made a successful connection to some server. */
void
http_register_netactivity_cb (void (*cb)(void))
{
netactivity_cb = cb;
}
/* Call the netactivity callback if any. */
static void
notify_netactivity (void)
{
if (netactivity_cb)
netactivity_cb ();
}
/* Free the TLS session associated with SESS, if any. */
static void
close_tls_session (http_session_t sess)
{
if (sess->tls_session)
{
#if HTTP_USE_NTBTLS
/* FIXME!!
Possibly, ntbtls_get_transport and close those streams.
Somehow get SOCK to call my_socket_unref.
*/
ntbtls_release (sess->tls_session);
#elif HTTP_USE_GNUTLS
my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
my_socket_unref (sock, NULL, NULL);
gnutls_deinit (sess->tls_session);
if (sess->certcred)
gnutls_certificate_free_credentials (sess->certcred);
#endif /*HTTP_USE_GNUTLS*/
xfree (sess->servername);
sess->tls_session = NULL;
}
}
/* Release a session. Take care not to release it while it is being
used by a http context object. */
static void
session_unref (int lnr, http_session_t sess)
{
if (!sess)
return;
log_assert (sess->magic == HTTP_SESSION_MAGIC);
sess->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
lnr, sess, sess->refcount);
if (sess->refcount)
return;
close_tls_session (sess);
sess->magic = 0xdeadbeef;
xfree (sess);
}
#define http_session_unref(a) session_unref (__LINE__, (a))
void
http_session_release (http_session_t sess)
{
http_session_unref (sess);
}
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
* HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
* HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
* HTTP_FLAG_TRUST_CFG - Also use CAs set with http_register_cfg_ca
* HTTP_FLAG_NO_CRL - Do not consult CRLs for https.
*/
gpg_error_t
http_session_new (http_session_t *r_session,
const char *intended_hostname, unsigned int flags,
http_verify_cb_t verify_cb, void *verify_cb_value)
{
gpg_error_t err;
http_session_t sess;
*r_session = NULL;
sess = xtrycalloc (1, sizeof *sess);
if (!sess)
return gpg_error_from_syserror ();
sess->magic = HTTP_SESSION_MAGIC;
sess->refcount = 1;
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
sess->connect_timeout = 0;
#if HTTP_USE_NTBTLS
{
(void)intended_hostname; /* Not needed because we do not preload
* certificates. */
err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
if (err)
{
log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
goto leave;
}
}
#elif HTTP_USE_GNUTLS
{
const char *errpos;
int rc;
strlist_t sl;
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
is_hkps_pool = (intended_hostname
&& !ascii_strcasecmp (intended_hostname,
get_default_keyserver (1)));
/* If the user has not specified a CA list, and they are looking
* for the hkps pool from sks-keyservers.net, then default to
* Kristian's certificate authority: */
if (!tls_ca_certlist && is_hkps_pool)
{
char *pemname = make_filename_try (gnupg_datadir (),
"sks-keyservers.netCA.pem", NULL);
if (!pemname)
{
err = gpg_error_from_syserror ();
log_error ("setting CA from file '%s' failed: %s\n",
pemname, gpg_strerror (err));
}
else
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, pemname, GNUTLS_X509_FMT_PEM);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
pemname, gnutls_strerror (rc));
xfree (pemname);
}
add_system_cas = 0;
}
/* Add configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_DEF))
{
for (sl = tls_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
if (!tls_ca_certlist && !is_hkps_pool)
add_system_cas = 1;
}
/* Add system certificates to the session. */
if (add_system_cas)
{
#if GNUTLS_VERSION_NUMBER >= 0x030014
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
if (rc < 0)
log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
else if (!shown)
{
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
#endif /* gnutls >= 3.0.20 */
}
/* Add other configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_CFG))
{
for (sl = cfg_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting extra CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
}
rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
if (rc < 0)
{
log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* A new session has the transport ptr set to (void*(-1), we need
it to be NULL. */
gnutls_transport_set_ptr (sess->tls_session, NULL);
rc = gnutls_priority_set_direct (sess->tls_session,
"NORMAL",
&errpos);
if (rc < 0)
{
log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
errpos, gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnutls_credentials_set (sess->tls_session,
GNUTLS_CRD_CERTIFICATE, sess->certcred);
if (rc < 0)
{
log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
{
(void)intended_hostname;
(void)flags;
}
#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
if (opt_debug > 1)
log_debug ("http.c:session_new: sess %p created\n", sess);
err = 0;
leave:
if (err)
http_session_unref (sess);
else
*r_session = sess;
return err;
}
/* Increment the reference count for session SESS. Passing NULL for
SESS is allowed. */
http_session_t
http_session_ref (http_session_t sess)
{
if (sess)
{
sess->refcount++;
if (opt_debug > 1)
log_debug ("http.c:session_ref: sess %p ref now %d\n",
sess, sess->refcount);
}
return sess;
}
void
http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *hostname,
const void **certs, size_t *certlens))
{
sess->cert_log_cb = cb;
}
/* Set the TIMEOUT in milliseconds for the connection's connect
* calls. Using 0 disables the timeout. */
void
http_session_set_timeout (http_session_t sess, unsigned int timeout)
{
sess->connect_timeout = timeout;
}
/* Start a HTTP retrieval and on success store at R_HD a context
pointer for completing the request and to wait for the response.
If HTTPHOST is not NULL it is used for the Host header instead of a
Host header derived from the URL. */
gpg_error_t
http_open (ctrl_t ctrl, http_t *r_hd, http_req_t reqtype, const char *url,
const char *httphost,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
http_t hd;
*r_hd = NULL;
if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = reqtype;
hd->flags = flags;
hd->session = http_session_ref (session);
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (ctrl, hd, httphost, auth, proxy, srvtag,
hd->session? hd->session->connect_timeout : 0,
headers);
if (err)
{
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. TIMEOUT is in milliseconds. */
gpg_error_t
http_raw_connect (ctrl_t ctrl, http_t *r_hd,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout)
{
gpg_error_t err = 0;
http_t hd;
cookie_t cookie;
*r_hd = NULL;
if ((flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = HTTP_REQ_OPAQUE;
hd->flags = flags;
/* Connect. */
{
assuan_fd_t sock;
err = connect_server (ctrl, server, port,
hd->flags, srvtag, timeout, &sock);
if (err)
{
xfree (hd);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (hd);
return err;
}
}
/* Setup estreams for reading and writing. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
goto leave;
}
hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
leave:
if (err)
{
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
my_socket_unref (hd->sock, NULL, NULL);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
void
http_start_data (http_t hd)
{
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string ("\r\n", "http.c:request-header:");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
}
else
es_fflush (hd->fp_write);
}
gpg_error_t
http_wait_response (http_t hd)
{
gpg_error_t err;
cookie_t cookie;
int use_tls;
/* Make sure that we are in the data. */
http_start_data (hd);
/* Close the write stream. Note that the reference counted socket
object keeps the actual system socket open. */
cookie = hd->write_cookie;
if (!cookie)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
use_tls = cookie->use_tls;
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it to NULL. */
hd->write_cookie = NULL;
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
if ((hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (FD2INT (hd->sock->fd), 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
cookie->sock = my_socket_ref (hd->sock);
cookie->session = http_session_ref (hd->session);
cookie->use_tls = use_tls;
hd->read_cookie = cookie;
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
http_session_unref (cookie->session);
xfree (cookie);
hd->read_cookie = NULL;
return err;
}
err = parse_response (hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
}
/* Convenience function to send a request and wait for the response.
Closes the handle on error. If PROXY is not NULL, this value will
be used as an HTTP proxy and any enabled $http_proxy gets
ignored. */
gpg_error_t
http_open_document (ctrl_t ctrl, http_t *r_hd, const char *document,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session,
const char *srvtag, strlist_t headers)
{
gpg_error_t err;
err = http_open (ctrl, r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
proxy, session, srvtag, headers);
if (err)
return err;
err = http_wait_response (*r_hd);
if (err)
http_close (*r_hd, 0);
return err;
}
void
http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
/* First remove the close notifications for the streams. */
if (hd->fp_read)
es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
if (hd->fp_write)
es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
/* Now we can close the streams. */
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
hd->magic = 0xdeadbeef;
http_release_parsed_uri (hd->uri);
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
xfree (hd->buffer);
xfree (hd);
}
estream_t
http_get_read_ptr (http_t hd)
{
return hd?hd->fp_read:NULL;
}
estream_t
http_get_write_ptr (http_t hd)
{
return hd?hd->fp_write:NULL;
}
unsigned int
http_get_status_code (http_t hd)
{
return hd?hd->status_code:0;
}
/* Return information pertaining to TLS. If TLS is not in use for HD,
NULL is returned. WHAT is used ask for specific information:
(NULL) := Only check whether TLS is in use. Returns an
unspecified string if TLS is in use. That string may
even be the empty string.
*/
const char *
http_get_tls_info (http_t hd, const char *what)
{
(void)what;
if (!hd)
return NULL;
return hd->uri->use_tls? "":NULL;
}
static gpg_error_t
parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls)
{
gpg_err_code_t ec;
*ret_uri = xtrycalloc (1, sizeof **ret_uri + 2 * strlen (uri) + 1);
if (!*ret_uri)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
strcpy ((*ret_uri)->buffer, uri);
strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri);
(*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1;
ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
if (ec)
{
http_release_parsed_uri (*ret_uri);
*ret_uri = NULL;
}
return gpg_err_make (default_errsource, ec);
}
/*
* Parse an URI and put the result into the newly allocated RET_URI.
* On success the caller must use http_release_parsed_uri() to
* releases the resources. If NO_SCHEME_CHECK is set, the function
* tries to parse the URL in the same way it would do for an HTTP
* style URI; this can for example be used for hkps or ldap schemes.
*/
gpg_error_t
http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check)
{
return parse_uri (ret_uri, uri, no_scheme_check, 0);
}
void
http_release_parsed_uri (parsed_uri_t uri)
{
if (uri)
{
uri_tuple_t r, r2;
for (r = uri->params; r; r = r2)
{
r2 = r->next;
xfree (r);
}
for (r = uri->query; r; r = r2)
{
r2 = r->next;
xfree (r);
}
xfree (uri);
}
}
static gpg_err_code_t
do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls)
{
uri_tuple_t *tail;
char *p, *p2, *p3, *pp;
int n;
p = uri->buffer;
n = strlen (uri->buffer);
/* Initialize all fields to an empty string or an empty list. */
uri->scheme = uri->host = uri->path = p + n;
uri->port = 0;
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
uri->is_ldap = 0;
uri->opaque = 0;
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
uri->off_host = 0;
uri->off_path = 0;
/* A quick validity check. */
if (strspn (p, VALID_URI_CHARS) != n)
return GPG_ERR_BAD_URI; /* Invalid characters found. */
if (!only_local_part)
{
/* Find the scheme. */
if (!(p2 = strchr (p, ':')) || p2 == p)
return GPG_ERR_BAD_URI; /* No scheme. */
*p2++ = 0;
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
uri->scheme = p;
if (!strcmp (uri->scheme, "http") && !force_tls)
{
uri->port = 80;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "hkp") && !force_tls)
{
uri->port = 11371;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
|| (force_tls && (!strcmp (uri->scheme, "http")
|| !strcmp (uri->scheme,"hkp"))))
{
uri->port = 443;
uri->is_http = 1;
uri->use_tls = 1;
}
else if (!no_scheme_check)
return GPG_ERR_INV_URI; /* Not an http style scheme. */
else if (!strcmp (uri->scheme, "ldap") && !force_tls)
{
uri->port = 389;
uri->is_ldap = 1;
}
else if (!strcmp (uri->scheme, "ldaps")
|| (force_tls && (!strcmp (uri->scheme, "ldap"))))
{
uri->port = 636;
uri->is_ldap = 1;
uri->use_tls = 1;
}
else if (!strcmp (uri->scheme, "ldapi")) /* LDAP via IPC. */
{
uri->port = 0;
uri->is_ldap = 1;
}
p = p2;
if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
p += 2;
if ((p2 = strchr (p, '/')))
{
if (p2 - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = p2 - uri->buffer;
*p2++ = 0;
}
else
{
n = (p - uri->buffer) + strlen (p);
if (n > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = n;
}
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
{
uri->auth = p;
*p3++ = '\0';
p = p3;
}
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
/* Handle an IPv6 literal */
if( *p == '[' && (p3=strchr( p, ']' )) )
{
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = (p + 1) - uri->buffer;
uri->v6lit = 1;
p = p3;
}
else
{
uri->host = p;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = p - uri->buffer;
}
if ((p3 = strchr (p, ':')))
{
*p3++ = '\0';
uri->port = atoi (p3);
uri->explicit_port = 1;
}
if ((n = remove_escapes (uri->host)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (uri->host))
return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */
p = p2 ? p2 : NULL;
}
else if (!no_scheme_check && (uri->is_http || uri->is_ldap))
return GPG_ERR_INV_URI; /* HTTP or LDAP w/o leading double slash. */
else
{
uri->opaque = 1;
uri->path = p;
if (is_onion_address (uri->path))
uri->onion = 1;
return 0;
}
} /* End global URI part. */
/* Parse the pathname part if any. */
if (p && *p)
{
/* TODO: Here we have to check params. */
/* Do we have a query part? */
if ((p2 = strchr (p, '?')))
*p2++ = 0;
uri->path = p;
if ((n = remove_escapes (p)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (p))
return GPG_ERR_BAD_URI; /* Path includes a Nul. */
p = p2 ? p2 : NULL;
/* Parse a query string if any. */
if (p && *p)
{
tail = &uri->query;
for (;;)
{
uri_tuple_t elem;
if ((p2 = strchr (p, '&')))
*p2++ = 0;
if (!(elem = parse_tuple (p)))
return GPG_ERR_BAD_URI;
*tail = elem;
tail = &elem->next;
if (!p2)
break; /* Ready. */
p = p2;
}
}
}
if (is_onion_address (uri->host))
uri->onion = 1;
return 0;
}
/*
* Remove all %xx escapes; this is done in-place. Returns: New length
* of the string.
*/
static int
remove_escapes (char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = (unsigned char*)string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
{
s++;
*p = *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
*p <<= 4;
s++;
*p |= *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Make sure to keep a string terminator. */
return n;
}
/* If SPECIAL is NULL this function escapes in forms mode. */
static size_t
escape_data (char *buffer, const void *data, size_t datalen,
const char *special)
{
int forms = !special;
const unsigned char *s;
size_t n = 0;
if (forms)
special = "%;?&=";
for (s = data; datalen; s++, datalen--)
{
if (forms && *s == ' ')
{
if (buffer)
*buffer++ = '+';
n++;
}
else if (forms && *s == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
}
else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
s++;
datalen--;
}
else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
{
if (buffer)
*(unsigned char*)buffer++ = *s;
n++;
}
else
{
if (buffer)
{
snprintf (buffer, 4, "%%%02X", *s);
buffer += 3;
}
n += 3;
}
}
return n;
}
static int
insert_escapes (char *buffer, const char *string,
const char *special)
{
return escape_data (buffer, string, strlen (string), special);
}
/* Allocate a new string from STRING using standard HTTP escaping as
well as escaping of characters given in SPECIALS. A common pattern
for SPECIALS is "%;?&=". However it depends on the needs, for
example "+" and "/: often needs to be escaped too. Returns NULL on
failure and sets ERRNO. If SPECIAL is NULL a dedicated forms
encoding mode is used. */
char *
http_escape_string (const char *string, const char *specials)
{
int n;
char *buf;
n = insert_escapes (NULL, string, specials);
buf = xtrymalloc (n+1);
if (buf)
{
insert_escapes (buf, string, specials);
buf[n] = 0;
}
return buf;
}
/* Allocate a new string from {DATA,DATALEN} using standard HTTP
escaping as well as escaping of characters given in SPECIALS. A
common pattern for SPECIALS is "%;?&=". However it depends on the
needs, for example "+" and "/: often needs to be escaped too.
Returns NULL on failure and sets ERRNO. If SPECIAL is NULL a
dedicated forms encoding mode is used. */
char *
http_escape_data (const void *data, size_t datalen, const char *specials)
{
int n;
char *buf;
n = escape_data (NULL, data, datalen, specials);
buf = xtrymalloc (n+1);
if (buf)
{
escape_data (buf, data, datalen, specials);
buf[n] = 0;
}
return buf;
}
static uri_tuple_t
parse_tuple (char *string)
{
char *p = string;
char *p2;
int n;
uri_tuple_t tuple;
if ((p2 = strchr (p, '=')))
*p2++ = 0;
if ((n = remove_escapes (p)) < 0)
return NULL; /* Bad URI. */
if (n != strlen (p))
return NULL; /* Name with a Nul in it. */
tuple = xtrycalloc (1, sizeof *tuple);
if (!tuple)
return NULL; /* Out of core. */
tuple->name = p;
if (!p2) /* We have only the name, so we assume an empty value string. */
{
tuple->value = p + strlen (p);
tuple->valuelen = 0;
tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
}
else /* Name and value. */
{
if ((n = remove_escapes (p2)) < 0)
{
xfree (tuple);
return NULL; /* Bad URI. */
}
tuple->value = p2;
tuple->valuelen = n;
}
return tuple;
}
/* Return true if STRING is likely "hostname:port" or only "hostname". */
static int
is_hostname_port (const char *string)
{
int colons = 0;
if (!string || !*string)
return 0;
for (; *string; string++)
{
if (*string == ':')
{
if (colons)
return 0;
if (!string[1])
return 0;
colons++;
}
else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
return 0; /* Invalid characters in hostname. */
else if (colons && !digitp (string))
return 0; /* Not a digit in the port. */
}
return 1;
}
/*
* Send a HTTP request to the server
* Returns 0 if the request was successful
*/
static gpg_error_t
send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth,
const char *proxy, const char *srvtag, unsigned int timeout,
strlist_t headers)
{
gpg_error_t err;
const char *server;
char *request, *p;
unsigned short port;
const char *http_proxy = NULL;
char *proxy_authstr = NULL;
char *authstr = NULL;
assuan_fd_t sock;
int have_http_proxy = 0;
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no TLS context available\n");
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
}
if (opt_debug)
log_debug ("Using TLS library: %s %s\n",
#if HTTP_USE_NTBTLS
"NTBTLS", ntbtls_check_version (NULL)
#elif HTTP_USE_GNUTLS
"GNUTLS", gnutls_check_version (NULL)
#endif /*HTTP_USE_GNUTLS*/
);
if ((hd->flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
server = *hd->uri->host ? hd->uri->host : "localhost";
port = hd->uri->port ? hd->uri->port : 80;
/* Try to use SNI. */
if (hd->uri->use_tls)
{
#if HTTP_USE_GNUTLS
int rc;
#endif
xfree (hd->session->servername);
hd->session->servername = xtrystrdup (httphost? httphost : server);
if (!hd->session->servername)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
return err;
}
#if HTTP_USE_NTBTLS
err = ntbtls_set_hostname (hd->session->tls_session,
hd->session->servername);
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
return err;
}
#elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
GNUTLS_NAME_DNS,
hd->session->servername,
strlen (hd->session->servername));
if (rc < 0)
log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
}
if ( (proxy && *proxy)
|| ( (hd->flags & HTTP_FLAG_TRY_PROXY)
&& (http_proxy = getenv (HTTP_PROXY_ENV))
&& *http_proxy ))
{
parsed_uri_t uri;
if (proxy)
http_proxy = proxy;
err = parse_uri (&uri, http_proxy, 0, 0);
if (gpg_err_code (err) == GPG_ERR_INV_URI
&& is_hostname_port (http_proxy))
{
/* Retry assuming a "hostname:port" string. */
char *tmpname = strconcat ("http://", http_proxy, NULL);
if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
err = 0;
xfree (tmpname);
}
if (err)
;
else if (!strcmp (uri->scheme, "http"))
have_http_proxy = 1;
else if (!strcmp (uri->scheme, "socks4")
|| !strcmp (uri->scheme, "socks5h"))
err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
else
err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
if (err)
{
log_error ("invalid HTTP proxy (%s): %s\n",
http_proxy, gpg_strerror (err));
return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
}
if (uri->auth)
{
remove_escapes (uri->auth);
proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
uri->auth, strlen(uri->auth));
if (!proxy_authstr)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
http_release_parsed_uri (uri);
return err;
}
}
err = connect_server (ctrl,
*uri->host ? uri->host : "localhost",
uri->port ? uri->port : 80,
hd->flags, NULL, timeout, &sock);
http_release_parsed_uri (uri);
}
else
{
err = connect_server (ctrl,
server, port, hd->flags, srvtag, timeout, &sock);
}
if (err)
{
xfree (proxy_authstr);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
if (have_http_proxy && hd->uri->use_tls)
{
int saved_flags;
cookie_t cookie;
/* Try to use the CONNECT method to proxy our TLS stream. */
request = es_bsprintf
("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
httphost ? httphost : server,
port,
httphost ? httphost : server,
port,
proxy_authstr ? proxy_authstr : "");
xfree (proxy_authstr);
proxy_authstr = NULL;
if (! request)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
cookie = xtrycalloc (1, sizeof *cookie);
if (! cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
return err;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (! hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
xfree (request);
hd->write_cookie = NULL;
return err;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (request);
request = NULL;
/* Make sure http_wait_response doesn't close the stream. */
saved_flags = hd->flags;
hd->flags &= ~HTTP_FLAG_SHUTDOWN;
/* Get the response. */
err = http_wait_response (hd);
/* Restore flags, destroy stream. */
hd->flags = saved_flags;
es_fclose (hd->fp_read);
hd->fp_read = NULL;
hd->read_cookie = NULL;
/* Reset state. */
hd->in_data = 0;
if (err)
return err;
if (hd->status_code != 200)
{
request = es_bsprintf
("CONNECT %s:%hu",
httphost ? httphost : server,
port);
log_error (_("error accessing '%s': http status %u\n"),
request ? request : "out of core",
http_get_status_code (hd));
xfree (request);
return gpg_error (GPG_ERR_NO_DATA);
}
/* We are done with the proxy, the code below will establish a
* TLS session and talk directly to the target server. */
http_proxy = NULL;
}
#if HTTP_USE_NTBTLS
if (hd->uri->use_tls)
{
estream_t in, out;
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
* to use es_fopencookie. */
# ifdef HAVE_W32_SYSTEM
in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb",
simple_cookie_functions);
# else
in = es_fdopen_nc (hd->sock->fd, "rb");
# endif
if (!in)
{
err = gpg_error_from_syserror ();
xfree (proxy_authstr);
return err;
}
# ifdef HAVE_W32_SYSTEM
out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb",
simple_cookie_functions);
# else
out = es_fdopen_nc (hd->sock->fd, "wb");
# endif
if (!out)
{
err = gpg_error_from_syserror ();
es_fclose (in);
xfree (proxy_authstr);
return err;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
if (err)
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
my_ntbtls_verify_cb, hd);
if (err)
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
hd->session->verify.done = 0;
/* Try the available verify callbacks until one returns success
* or a real error. Note that NTBTLS does the verification
* during the handshake via */
err = 0; /* Fixme check that the CB has been called. */
if (hd->session->verify_cb
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
hd->session->tls_session);
if (tls_callback
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = tls_callback (hd, hd->session, 0);
if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
&& gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
xfree (proxy_authstr);
return err;
}
}
#elif HTTP_USE_GNUTLS
if (hd->uri->use_tls)
{
int rc;
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
my_gnutls_read);
gnutls_transport_set_push_function (hd->session->tls_session,
my_gnutls_write);
handshake_again:
do
{
rc = gnutls_handshake (hd->session->tls_session);
}
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc < 0)
{
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
|| rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
gnutls_alert_description_t alertno;
const char *alertstr;
alertno = gnutls_alert_get (hd->session->tls_session);
alertstr = gnutls_alert_get_name (alertno);
log_info ("TLS handshake %s: %s (alert %d)\n",
rc == GNUTLS_E_WARNING_ALERT_RECEIVED
? "warning" : "failed",
alertstr, (int)alertno);
if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
log_info (" (sent server name '%s')\n", server);
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
goto handshake_again;
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
xfree (proxy_authstr);
return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
}
hd->session->verify.done = 0;
if (tls_callback)
err = tls_callback (hd, hd->session, 0);
else
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
xfree (proxy_authstr);
return err;
}
}
#endif /*HTTP_USE_GNUTLS*/
if (auth || hd->uri->auth)
{
char *myauth;
if (auth)
{
myauth = xtrystrdup (auth);
if (!myauth)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
remove_escapes (myauth);
}
else
{
remove_escapes (hd->uri->auth);
myauth = hd->uri->auth;
}
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
if (auth)
xfree (myauth);
if (!authstr)
{
xfree (proxy_authstr);
return gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
}
}
p = build_rel_path (hd->uri);
if (!p)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (http_proxy && *http_proxy)
{
request = es_bsprintf
("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
hd->uri->use_tls? "https" : "http",
httphost? httphost : server,
port, *p == '/' ? "" : "/", p,
authstr ? authstr : "",
proxy_authstr ? proxy_authstr : "");
}
else
{
char portstr[35];
if (port == (hd->uri->use_tls? 443 : 80))
*portstr = 0;
else
snprintf (portstr, sizeof portstr, ":%u", port);
request = es_bsprintf
("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
*p == '/' ? "" : "/", p,
httphost? httphost : server,
portstr,
authstr? authstr:"");
}
xfree (p);
if (!request)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (authstr);
xfree (proxy_authstr);
return err;
}
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
goto leave;
}
cookie->sock = my_socket_ref (hd->sock);
hd->write_cookie = cookie;
cookie->use_tls = hd->uri->use_tls;
cookie->session = http_session_ref (hd->session);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
my_socket_unref (cookie->sock, NULL, NULL);
xfree (cookie);
hd->write_cookie = NULL;
}
else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
if (!err)
{
for (;headers; headers=headers->next)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (headers->d, "http.c:request-header:");
if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
|| (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
break;
}
}
}
}
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
return err;
}
/*
* Build the relative path from the parsed URI. Minimal
* implementation. May return NULL in case of memory failure; errno
* is then set accordingly.
*/
static char *
build_rel_path (parsed_uri_t uri)
{
uri_tuple_t r;
char *rel_path, *p;
int n;
/* Count the needed space. */
n = insert_escapes (NULL, uri->path, "%;?&");
/* TODO: build params. */
for (r = uri->query; r; r = r->next)
{
n++; /* '?'/'&' */
n += insert_escapes (NULL, r->name, "%;?&=");
if (!r->no_value)
{
n++; /* '=' */
n += insert_escapes (NULL, r->value, "%;?&=");
}
}
n++;
/* Now allocate and copy. */
p = rel_path = xtrymalloc (n);
if (!p)
return NULL;
n = insert_escapes (p, uri->path, "%;?&");
p += n;
/* TODO: add params. */
for (r = uri->query; r; r = r->next)
{
*p++ = r == uri->query ? '?' : '&';
n = insert_escapes (p, r->name, "%;?&=");
p += n;
if (!r->no_value)
{
*p++ = '=';
/* TODO: Use valuelen. */
n = insert_escapes (p, r->value, "%;?&=");
p += n;
}
}
*p = 0;
return rel_path;
}
/* Transform a header name into a standard capitalized format; e.g.
"Content-Type". Conversion stops at the colon. As usual we don't
use the localized versions of ctype.h. */
static void
capitalize_header_name (char *name)
{
int first = 1;
for (; *name && *name != ':'; name++)
{
if (*name == '-')
first = 1;
else if (first)
{
if (*name >= 'a' && *name <= 'z')
*name = *name - 'a' + 'A';
first = 0;
}
else if (*name >= 'A' && *name <= 'Z')
*name = *name - 'A' + 'a';
}
}
/* Store an HTTP header line in LINE away. Line continuation is
supported as well as merging of headers with the same name. This
function may modify LINE. */
static gpg_err_code_t
store_header (http_t hd, char *line)
{
size_t n;
char *p, *value;
header_t h;
n = strlen (line);
if (n && line[n-1] == '\n')
{
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
}
if (!n) /* we are never called to hit this. */
return GPG_ERR_BUG;
if (*line == ' ' || *line == '\t')
{
/* Continuation. This won't happen too often as it is not
recommended. We use a straightforward implementation. */
if (!hd->headers)
return GPG_ERR_PROTOCOL_VIOLATION;
n += strlen (hd->headers->value);
p = xtrymalloc (n+1);
if (!p)
return gpg_err_code_from_syserror ();
strcpy (stpcpy (p, hd->headers->value), line);
xfree (hd->headers->value);
hd->headers->value = p;
return 0;
}
capitalize_header_name (line);
p = strchr (line, ':');
if (!p)
return GPG_ERR_PROTOCOL_VIOLATION;
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, line) )
break;
if (h)
{
/* We have already seen a line with that name. Thus we assume
* it is a comma separated list and merge them. */
p = strconcat (h->value, ",", value, NULL);
if (!p)
return gpg_err_code_from_syserror ();
xfree (h->value);
h->value = p;
return 0;
}
/* Append a new header. */
h = xtrymalloc (sizeof *h + strlen (line));
if (!h)
return gpg_err_code_from_syserror ();
strcpy (h->name, line);
h->value = xtrymalloc (strlen (value)+1);
if (!h->value)
{
xfree (h);
return gpg_err_code_from_syserror ();
}
strcpy (h->value, value);
h->next = hd->headers;
hd->headers = h;
return 0;
}
/* Return the header NAME from the last response. The returned value
is valid as along as HD has not been closed and no other request
has been send. If the header was not found, NULL is returned. NAME
must be canonicalized, that is the first letter of each dash
delimited part must be uppercase and all other letters lowercase. */
const char *
http_get_header (http_t hd, const char *name)
{
header_t h;
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, name) )
return h->value;
return NULL;
}
/* Return a newly allocated and NULL terminated array with pointers to
header names. The array must be released with xfree() and its
content is only values as long as no other request has been
send. */
const char **
http_get_header_names (http_t hd)
{
const char **array;
size_t n;
header_t h;
for (n=0, h = hd->headers; h; h = h->next)
n++;
array = xtrycalloc (n+1, sizeof *array);
if (array)
{
for (n=0, h = hd->headers; h; h = h->next)
array[n++] = h->name;
}
return array;
}
/*
* Parse the response from a server.
* Returns: Errorcode and sets some files in the handle
*/
static gpg_err_code_t
parse_response (http_t hd)
{
char *line, *p, *p2;
size_t maxlen, len;
cookie_t cookie = hd->read_cookie;
const char *s;
/* Delete old header lines. */
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
/* Wait for the status line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
if (!maxlen)
return GPG_ERR_TRUNCATED; /* Line has been truncated. */
if (!len)
return GPG_ERR_EOF;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (line, "http.c:response:\n");
}
while (!*line);
if ((p = strchr (line, '/')))
*p++ = 0;
if (!p || strcmp (line, "HTTP"))
return 0; /* Assume http 0.9. */
if ((p2 = strpbrk (p, " \t")))
{
*p2++ = 0;
p2 += strspn (p2, " \t");
}
if (!p2)
return 0; /* Also assume http 0.9. */
p = p2;
/* TODO: Add HTTP version number check. */
if ((p2 = strpbrk (p, " \t")))
*p2++ = 0;
if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
|| !isdigit ((unsigned int)p[2]) || p[3])
{
/* Malformed HTTP status code - assume http 0.9. */
hd->is_http_0_9 = 1;
hd->status_code = 200;
return 0;
}
hd->status_code = atoi (p);
/* Skip all the header lines and wait for the empty line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_err_code_from_syserror (); /* Out of core. */
/* Note, that we can silently ignore truncated lines. */
if (!len)
return GPG_ERR_EOF;
/* Trim line endings of empty lines. */
if ((*line == '\r' && line[1] == '\n') || *line == '\n')
*line = 0;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_info ("http.c:RESP: '%.*s'\n",
(int)strlen(line)-(*line&&line[1]?2:0),line);
if (*line)
{
gpg_err_code_t ec = store_header (hd, line);
if (ec)
return ec;
}
}
while (len && *line);
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
s = http_get_header (hd, "Content-Length");
if (s)
{
cookie->content_length_valid = 1;
cookie->content_length = string_to_u64 (s);
}
}
return 0;
}
#if 0
static int
start_server ()
{
struct sockaddr_in mya;
struct sockaddr_in peer;
int fd, client;
fd_set rfds;
int addrlen;
int i;
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
log_error ("socket() failed: %s\n", strerror (errno));
return -1;
}
i = 1;
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
mya.sin_family = AF_INET;
memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
mya.sin_port = htons (11371);
if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
{
log_error ("bind to port 11371 failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
if (listen (fd, 5))
{
log_error ("listen failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
for (;;)
{
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* ignore any errors */
if (!FD_ISSET (fd, &rfds))
continue;
addrlen = sizeof peer;
client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
if (client == -1)
continue; /* oops */
log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
fflush (stdout);
fflush (stderr);
if (!fork ())
{
int c;
FILE *fp;
fp = fdopen (client, "r");
while ((c = getc (fp)) != EOF)
putchar (c);
fclose (fp);
exit (0);
}
sock_close (client);
}
return 0;
}
#endif
/* Return true if SOCKS shall be used. This is the case if tor_mode
* is enabled and the desired address is not the loopback address.
* This function is basically a copy of the same internal function in
* Libassuan. */
static int
use_socks (struct sockaddr_storage *addr)
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
return 0; /* Not in Tor mode. */
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
const unsigned char *s;
int i;
s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->ss_family == AF_INET)
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
/* Wrapper around assuan_sock_new which takes the domain from an
* address parameter. */
static assuan_fd_t
my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
{
int domain;
if (use_socks (addr))
{
/* Libassaun always uses 127.0.0.1 to connect to the socks
* server (i.e. the Tor daemon). */
domain = AF_INET;
}
else
domain = addr->ss_family;
return assuan_sock_new (domain, type, proto);
}
/* Call WSAGetLastError and map it to a libgpg-error. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
my_wsagetlasterror (void)
{
int wsaerr;
gpg_err_code_t ec;
wsaerr = WSAGetLastError ();
switch (wsaerr)
{
case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break;
case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break;
case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break;
case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break;
case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break;
case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break;
case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break;
case WSAEISCONN: ec = GPG_ERR_EISCONN; break;
case WSAEALREADY: ec = GPG_ERR_EALREADY; break;
case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break;
default: ec = GPG_ERR_EIO; break;
}
return gpg_err_make (default_errsource, ec);
}
#endif /*HAVE_W32_SYSTEM*/
/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
* be established within TIMEOUT milliseconds. 0 indicates the
* system's default timeout. The other args are the usual connect
* args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
* another error code for other errors. On timeout the caller needs
* to close the socket as soon as possible to stop an ongoing
* handshake.
*
* This implementation is for well-behaving systems; see Stevens,
* Network Programming, 2nd edition, Vol 1, 15.4. */
static gpg_error_t
connect_with_timeout (assuan_fd_t sock,
struct sockaddr *addr, int addrlen,
unsigned int timeout)
{
gpg_error_t err;
int syserr;
socklen_t slen;
fd_set rset, wset;
struct timeval tval;
int n;
#ifndef HAVE_W32_SYSTEM
int oflags;
# define RESTORE_BLOCKING() do { \
fcntl (sock, F_SETFL, oflags); \
} while (0)
#else /*HAVE_W32_SYSTEM*/
# define RESTORE_BLOCKING() do { \
unsigned long along = 0; \
ioctlsocket (FD2INT (sock), FIONBIO, &along); \
} while (0)
#endif /*HAVE_W32_SYSTEM*/
if (!timeout)
{
/* Shortcut. */
if (assuan_sock_connect (sock, addr, addrlen))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
return err;
}
/* Switch the socket into non-blocking mode. */
#ifdef HAVE_W32_SYSTEM
{
unsigned long along = 1;
if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
return my_wsagetlasterror ();
}
#else
oflags = fcntl (sock, F_GETFL, 0);
if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
#endif
/* Do the connect. */
if (!assuan_sock_connect (sock, addr, addrlen))
{
/* Immediate connect. Restore flags. */
RESTORE_BLOCKING ();
return 0; /* Success. */
}
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (gpg_err_code (err) != GPG_ERR_EINPROGRESS
#ifdef HAVE_W32_SYSTEM
&& gpg_err_code (err) != GPG_ERR_EAGAIN
#endif
)
{
RESTORE_BLOCKING ();
return err;
}
FD_ZERO (&rset);
FD_SET (FD2INT (sock), &rset);
wset = rset;
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000) * 1000;
n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval);
if (n < 0)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
RESTORE_BLOCKING ();
return err;
}
if (!n)
{
/* Timeout: We do not restore the socket flags on timeout
* because the caller is expected to close the socket. */
return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
}
if (!FD_ISSET (sock, &rset) && !FD_ISSET (sock, &wset))
{
/* select misbehaved. */
return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
}
slen = sizeof (syserr);
if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
(void*)&syserr, &slen) < 0)
{
/* Assume that this is Solaris which returns the error in ERRNO. */
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
else if (syserr)
err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
else
err = 0; /* Connected. */
RESTORE_BLOCKING ();
return err;
#undef RESTORE_BLOCKING
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
* TIMEOUT is the connect timeout in milliseconds. Note that the
* function tries to connect to all known addresses and the timeout is
* for each one. */
static gpg_error_t
connect_server (ctrl_t ctrl, const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout,
assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
unsigned int srvcount = 0;
int hostfound = 0;
int anyhostaddr = 0;
int srv, connected, v4_valid, v6_valid;
gpg_error_t last_err = 0;
struct srventry *serverlist = NULL;
*r_sock = ASSUAN_INVALID_FD;
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
init_sockets ();
#endif /*Windows*/
check_inet_support (&v4_valid, &v6_valid);
/* Onion addresses require special treatment. */
if (is_onion_address (server))
{
#ifdef ASSUAN_SOCK_TOR
if (opt_debug)
log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
server, port);
sock = assuan_sock_connect_byname (server, port, 0, NULL,
ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
(errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
: gpg_err_code_from_syserror ());
log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
return err;
}
notify_netactivity ();
*r_sock = sock;
return 0;
#else /*!ASSUAN_SOCK_TOR*/
err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
return ASSUAN_INVALID_FD;
#endif /*!HASSUAN_SOCK_TOR*/
}
/* Do the SRV thing */
if (srvtag)
{
err = get_dns_srv (ctrl, server, srvtag, NULL, &serverlist, &srvcount);
if (err)
log_info ("getting '%s' SRV for '%s' failed: %s\n",
srvtag, server, gpg_strerror (err));
/* Note that on error SRVCOUNT is zero. */
err = 0;
}
if (!serverlist)
{
/* Either we're not using SRV, or the SRV lookup failed. Make
up a fake SRV record. */
serverlist = xtrycalloc (1, sizeof *serverlist);
if (!serverlist)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
serverlist->port = port;
strncpy (serverlist->target, server, DIMof (struct srventry, target));
serverlist->target[DIMof (struct srventry, target)-1] = '\0';
srvcount = 1;
}
connected = 0;
for (srv=0; srv < srvcount && !connected; srv++)
{
dns_addrinfo_t aibuf, ai;
if (opt_debug)
log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
serverlist[srv].target, port);
err = resolve_dns_name (ctrl,
serverlist[srv].target, port, 0, SOCK_STREAM,
&aibuf, NULL);
if (err)
{
log_info ("resolving '%s' failed: %s\n",
serverlist[srv].target, gpg_strerror (err));
last_err = err;
continue; /* Not found - try next one. */
}
hostfound = 1;
for (ai = aibuf; ai && !connected; ai = ai->next)
{
if (ai->family == AF_INET
&& ((flags & HTTP_FLAG_IGNORE_IPv4) || !v4_valid))
continue;
if (ai->family == AF_INET6
&& ((flags & HTTP_FLAG_IGNORE_IPv6) || !v6_valid))
continue;
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
log_error ("error creating socket: %s\n", gpg_strerror (err));
free_dns_addrinfo (aibuf);
xfree (serverlist);
return err;
}
anyhostaddr = 1;
err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
ai->addrlen, timeout);
if (err)
{
last_err = err;
}
else
{
connected = 1;
notify_netactivity ();
}
}
free_dns_addrinfo (aibuf);
}
xfree (serverlist);
if (!connected)
{
if (!hostfound)
log_error ("can't connect to '%s': %s\n",
server, "host not found");
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
else
{
#ifdef HAVE_W32_SYSTEM
log_error ("can't connect to '%s': ec=%d\n",
server, (int)WSAGetLastError());
#else
log_error ("can't connect to '%s': %s\n",
server, gpg_strerror (last_err));
#endif
}
err = last_err? last_err : gpg_err_make (default_errsource,
GPG_ERR_UNKNOWN_HOST);
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
return err;
}
*r_sock = sock;
return 0;
}
/* Helper to read from a socket. This handles npth things and
* EINTR. */
static gpgrt_ssize_t
read_server (assuan_fd_t sock, void *buffer, size_t size)
{
int nread;
do
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nread = recv (FD2INT (sock), buffer, size, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nread = npth_read (sock, buffer, size);
# else
nread = read (sock, buffer, size);
# endif
#endif /*!HAVE_W32_SYSTEM*/
}
while (nread == -1 && errno == EINTR);
return nread;
}
static gpg_error_t
write_server (assuan_fd_t sock, const char *data, size_t length)
{
int nleft;
int nwritten;
nleft = length;
while (nleft > 0)
{
#if defined(HAVE_W32_SYSTEM)
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock), data, nleft, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
if ( nwritten == SOCKET_ERROR )
{
log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
return gpg_error (GPG_ERR_NETWORK);
}
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nwritten = npth_write (sock, data, nleft);
# else
nwritten = write (sock, data, nleft);
# endif
if (nwritten == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("network write failed: %s\n", strerror (errno));
return gpg_error_from_syserror ();
}
#endif /*!HAVE_W32_SYSTEM*/
nleft -= nwritten;
data += nwritten;
}
return 0;
}
/* Read handler for estream. */
static gpgrt_ssize_t
cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
if (c->content_length_valid)
{
if (!c->content_length)
return 0; /* EOF */
if (c->content_length < size)
size = c->content_length;
}
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
nread = es_fread (buffer, 1, size, in);
if (opt_debug)
log_debug ("TLS network read: %d/%zu\n", nread, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
nread = gnutls_record_recv (c->session->tls_session, buffer, size);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
goto again;
if (nread == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
if (nread == GNUTLS_E_REHANDSHAKE)
goto again; /* A client is allowed to just ignore this request. */
if (nread == GNUTLS_E_PREMATURE_TERMINATION)
{
/* The server terminated the connection. Close the TLS
session, and indicate EOF using a short read. */
close_tls_session (c->session);
return 0;
}
log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
gpg_err_set_errno (EIO);
return -1;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
nread = read_server (c->sock->fd, buffer, size);
}
if (c->content_length_valid && nread > 0)
{
if (nread < c->content_length)
c->content_length -= nread;
else
c->content_length = 0;
}
return (gpgrt_ssize_t)nread;
}
/* Write handler for estream. */
static gpgrt_ssize_t
cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
cookie_t c = cookie;
int nwritten = 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
if (size == 0)
es_fflush (out);
else
nwritten = es_fwrite (buffer, 1, size, out);
if (opt_debug)
log_debug ("TLS network write: %d/%zu\n", nwritten, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
int nleft = size;
while (nleft > 0)
{
nwritten = gnutls_record_send (c->session->tls_session,
buffer, nleft);
if (nwritten <= 0)
{
if (nwritten == GNUTLS_E_INTERRUPTED)
continue;
if (nwritten == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("TLS network write failed: %s\n",
gnutls_strerror (nwritten));
gpg_err_set_errno (EIO);
return -1;
}
nleft -= nwritten;
buffer += nwritten;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
}
return (gpgrt_ssize_t)nwritten;
}
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t
simple_cookie_read (void *cookie, void *buffer, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
return read_server (sock, buffer, size);
}
static gpgrt_ssize_t
simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
const char *buffer = buffer_arg;
int nwritten;
if (write_server (sock, buffer, size))
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
return (gpgrt_ssize_t)nwritten;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HTTP_USE_GNUTLS
/* Wrapper for gnutls_bye used by my_socket_unref. */
static void
send_gnutls_bye (void *opaque)
{
tls_session_t tls_session = opaque;
int ret;
again:
do
ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
while (ret == GNUTLS_E_INTERRUPTED);
if (ret == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
}
#endif /*HTTP_USE_GNUTLS*/
/* Close handler for estream. */
static int
cookie_close (void *cookie)
{
cookie_t c = cookie;
if (!c)
return 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
/* FIXME!! Possibly call ntbtls_close_notify for close
of write stream. */
my_socket_unref (c->sock, NULL, NULL);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
else
#endif /*HTTP_USE_GNUTLS*/
if (c->sock)
my_socket_unref (c->sock, NULL, NULL);
if (c->session)
http_session_unref (c->session);
xfree (c);
return 0;
}
/* Verify the credentials of the server. Returns 0 on success and
store the result in the session object. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
#if HTTP_USE_GNUTLS
static const char errprefix[] = "TLS verification of peer failed";
int rc;
unsigned int status;
const char *hostname;
const gnutls_datum_t *certlist;
unsigned int certlistlen;
gnutls_x509_crt_t cert;
gpg_error_t err = 0;
sess->verify.done = 1;
sess->verify.status = 0;
sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
{
log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
return gpg_error (GPG_ERR_GENERAL);
}
rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
if (rc)
{
log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
else if (status)
{
log_error ("%s: status=0x%04x\n", errprefix, status);
#if GNUTLS_VERSION_NUMBER >= 0x030104
{
gnutls_datum_t statusdat;
if (!gnutls_certificate_verification_status_print
(status, GNUTLS_CRT_X509, &statusdat, 0))
{
log_info ("%s: %s\n", errprefix, statusdat.data);
gnutls_free (statusdat.data);
}
}
#endif /*gnutls >= 3.1.4*/
sess->verify.status = status;
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
hostname = sess->servername;
if (!hostname || !strchr (hostname, '.'))
{
log_error ("%s: %s\n", errprefix, "hostname missing");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
if (!certlistlen)
{
log_error ("%s: %s\n", errprefix, "server did not send a certificate");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
/* Need to stop here. */
if (err)
return err;
}
rc = gnutls_x509_crt_init (&cert);
if (rc < 0)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
if (err)
return err;
}
rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
if (rc < 0)
{
log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
if (!gnutls_x509_crt_check_hostname (cert, hostname))
{
log_error ("%s: %s\n", errprefix, "hostname does not match");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
gnutls_x509_crt_deinit (cert);
if (!err)
sess->verify.rc = 0;
if (sess->cert_log_cb)
{
const void *bufarr[10];
size_t buflenarr[10];
size_t n;
for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
{
bufarr[n] = certlist[n].data;
buflenarr[n] = certlist[n].size;
}
bufarr[n] = NULL;
buflenarr[n] = 0;
sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
}
return err;
#else /*!HTTP_USE_GNUTLS*/
(void)sess;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Return the first query variable with the specified key. If there
is no such variable, return NULL. */
struct uri_tuple_s *
uri_query_lookup (parsed_uri_t uri, const char *key)
{
struct uri_tuple_s *t;
for (t = uri->query; t; t = t->next)
if (strcmp (t->name, key) == 0)
return t;
return NULL;
}
/* Return true if both URI point to the same host for the purpose of
* redirection check. A is the original host and B the host given in
* the Location header. As a temporary workaround a fixed list of
* exceptions is also consulted. */
static int
same_host_p (parsed_uri_t a, parsed_uri_t b)
{
static struct
{
const char *from; /* NULL uses the last entry from the table. */
const char *to;
} allow[] =
{
{ "protonmail.com", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "protonmail.ch", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "pm.me", "api.protonmail.ch" }
};
static const char *subdomains[] =
{
"openpgpkey."
};
int i;
const char *from;
if (!a->host || !b->host)
return 0;
if (!ascii_strcasecmp (a->host, b->host))
return 1;
from = NULL;
for (i=0; i < DIM (allow); i++)
{
if (allow[i].from)
from = allow[i].from;
if (!from)
continue;
if (!ascii_strcasecmp (from, a->host)
&& !ascii_strcasecmp (allow[i].to, b->host))
return 1;
}
/* Also consider hosts the same if they differ only in a subdomain;
* in both direction. This allows to have redirection between the
* WKD advanced and direct lookup methods. */
for (i=0; i < DIM (subdomains); i++)
{
const char *subdom = subdomains[i];
size_t subdomlen = strlen (subdom);
if (!ascii_strncasecmp (a->host, subdom, subdomlen)
&& !ascii_strcasecmp (a->host + subdomlen, b->host))
return 1;
if (!ascii_strncasecmp (b->host, subdom, subdomlen)
&& !ascii_strcasecmp (b->host + subdomlen, a->host))
return 1;
}
return 0;
}
/* Prepare a new URL for a HTTP redirect. INFO has flags controlling
* the operation, STATUS_CODE is used for diagnostics, LOCATION is the
* value of the "Location" header, and R_URL reveives the new URL on
* success or NULL or error. Note that INFO->ORIG_URL is
* required. */
gpg_error_t
http_prepare_redirect (http_redir_info_t *info, unsigned int status_code,
const char *location, char **r_url)
{
gpg_error_t err;
parsed_uri_t locuri;
parsed_uri_t origuri;
char *newurl;
char *p;
*r_url = NULL;
if (!info || !info->orig_url)
return gpg_error (GPG_ERR_INV_ARG);
if (!info->silent)
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
info->orig_url, location? location:"[none]", status_code);
if (!info->redirects_left)
{
if (!info->silent)
log_error (_("too many redirections\n"));
return gpg_error (GPG_ERR_NO_DATA);
}
info->redirects_left--;
if (!location || !*location)
return gpg_error (GPG_ERR_NO_DATA);
err = http_parse_uri (&locuri, location, 0);
if (err)
return err;
/* Make sure that an onion address only redirects to another
* onion address, or that a https address only redirects to a
* https address. */
if (info->orig_onion && !locuri->onion)
{
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect from onion to non-onion address"
" rejected",
err);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_FORBIDDEN);
}
if (!info->allow_downgrade && info->orig_https && !locuri->use_tls)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect '%s' to '%s' rejected",
err, info->orig_url, location);
http_release_parsed_uri (locuri);
return err;
}
if (info->trust_location)
{
/* We trust the Location - return it verbatim. */
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (locuri);
return err;
}
}
else if ((err = http_parse_uri (&origuri, info->orig_url, 0)))
{
http_release_parsed_uri (locuri);
return err;
}
else if (same_host_p (origuri, locuri))
{
/* The host is the same or on an exception list and thus we can
* take the location verbatim. */
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (locuri);
return err;
}
}
else
{
/* We take only the host and port from the URL given in the
* Location. This limits the effects of redirection attacks by
* rogue hosts returning an URL to servers in the client's own
* network. We don't even include the userinfo because they
* should be considered similar to the path and query parts.
*/
if (!(locuri->off_path - locuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
if (!(origuri->off_path - origuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
newurl = xtrymalloc (strlen (origuri->original)
+ (locuri->off_path - locuri->off_host) + 1);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return err;
}
/* Build new URL from
* uriguri: scheme userinfo ---- ---- path rest
* locuri: ------ -------- host port ---- ----
*/
p = newurl;
memcpy (p, origuri->original, origuri->off_host);
p += origuri->off_host;
memcpy (p, locuri->original + locuri->off_host,
(locuri->off_path - locuri->off_host));
p += locuri->off_path - locuri->off_host;
strcpy (p, origuri->original + origuri->off_path);
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
if (!info->silent)
log_info (_("redirection changed to '%s'\n"), newurl);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect_cleanup %u"
" changed from '%s' to '%s'",
0, info->orig_url, newurl);
}
*r_url = newurl;
return 0;
}
/* Return string describing the http STATUS. Returns an empty string
* for an unknown status. */
const char *
http_status2string (unsigned int status)
{
switch (status)
{
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP version Not Supported";
case 506: return "Variant Also Negation";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
}
return "";
}
diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c
index d1e7dc0c4..31de6e030 100644
--- a/dirmngr/ldap-wrapper.c
+++ b/dirmngr/ldap-wrapper.c
@@ -1,928 +1,928 @@
/* ldap-wrapper.c - LDAP access via a wrapper process
* Copyright (C) 2004, 2005, 2007, 2008, 2018 g10 Code GmbH
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
* We can't use LDAP directly for these reasons:
*
* 1. The LDAP library is linked to separate crypto library like
- * OpenSSL and even if it is linked to the libary we use in dirmngr
+ * OpenSSL and even if it is linked to the library we use in dirmngr
* (ntbtls or gnutls) it is sometimes a different version of that
- * libary with all the surprising failures you may get due to this.
+ * library with all the surprising failures you may get due to this.
*
* 2. It is huge library in particular if TLS comes into play. So
* problems with unfreed memory might turn up and we don't want
* this in a long running daemon.
*
* 3. There is no easy way for timeouts. In particular the timeout
* value does not work for DNS lookups (well, this is usual) and it
* seems not to work while loading a large attribute like a
* CRL. Having a separate process allows us to either tell the
* process to commit suicide or have our own housekepping function
* kill it after some time. The latter also allows proper
* cancellation of a query at any point of time.
*
* 4. Given that we are going out to the network and usually get back
* a long response, the fork/exec overhead is acceptable.
*
* Note that under WindowsCE the number of processes is strongly
* limited (32 processes including the kernel processes) and thus we
* don't use the process approach but implement a different wrapper in
* ldap-wrapper-ce.c.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <npth.h>
#include "dirmngr.h"
#include "../common/exechelp.h"
#include "misc.h"
#include "ldap-wrapper.h"
#ifdef HAVE_W32_SYSTEM
#define setenv(a,b,c) SetEnvironmentVariable ((a),(b))
#else
#define pth_close(fd) close(fd)
#endif
/* In case sysconf does not return a value we need to have a limit. */
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */
#define TIMERTICK_INTERVAL 2
/* To keep track of the LDAP wrapper state we use this structure. */
struct wrapper_context_s
{
struct wrapper_context_s *next;
pid_t pid; /* The pid of the wrapper process. */
int printable_pid; /* Helper to print diagnostics after the process has
* been cleaned up. */
estream_t fp; /* Connected with stdout of the ldap wrapper. */
gpg_error_t fp_err; /* Set to the gpg_error of the last read error
* if any. */
estream_t log_fp; /* Connected with stderr of the ldap wrapper. */
ctrl_t ctrl; /* Connection data. */
int ready; /* Internally used to mark to be removed contexts. */
ksba_reader_t reader;/* The ksba reader object or NULL. */
char *line; /* Used to print the log lines (malloced). */
size_t linesize; /* Allocated size of LINE. */
size_t linelen; /* Use size of LINE. */
time_t stamp; /* The last time we noticed ativity. */
int reaper_idx; /* Private to ldap_wrapper_thread. */
};
/* We keep a global list of spawned wrapper process. A separate
* thread makes use of this list to log error messages and to watch
* out for finished processes. Access to list is protected by a
* mutex. The condition variable is used to wakeup the reaper
* thread. */
static struct wrapper_context_s *reaper_list;
static npth_mutex_t reaper_list_mutex = NPTH_MUTEX_INITIALIZER;
static npth_cond_t reaper_run_cond = NPTH_COND_INITIALIZER;
/* We need to know whether we are shutting down the process. */
static int shutting_down;
/* Close the estream fp and set it to NULL. */
#define SAFE_CLOSE(fp) \
do { estream_t _fp = fp; es_fclose (_fp); fp = NULL; } while (0)
static void
lock_reaper_list (void)
{
if (npth_mutex_lock (&reaper_list_mutex))
log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_syserror ()));
}
static void
unlock_reaper_list (void)
{
if (npth_mutex_unlock (&reaper_list_mutex))
log_fatal ("%s: failed to release mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_syserror ()));
}
/* Read a fixed amount of data from READER into BUFFER. */
static gpg_error_t
read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
{
gpg_error_t err;
size_t nread;
while (count)
{
err = ksba_reader_read (reader, buffer, count, &nread);
if (err)
return err;
buffer += nread;
count -= nread;
}
return 0;
}
/* Release the wrapper context and kill a running wrapper process. */
static void
destroy_wrapper (struct wrapper_context_s *ctx)
{
if (ctx->pid != (pid_t)(-1))
{
gnupg_kill_process (ctx->pid);
gnupg_release_process (ctx->pid);
}
ksba_reader_release (ctx->reader);
SAFE_CLOSE (ctx->fp);
SAFE_CLOSE (ctx->log_fp);
xfree (ctx->line);
xfree (ctx);
}
/* Print the content of LINE to the log stream but make sure to only
print complete lines. Using NULL for LINE will flush any pending
output. LINE may be modified by this function. */
static void
print_log_line (struct wrapper_context_s *ctx, char *line)
{
char *s;
size_t n;
if (!line)
{
if (ctx->line && ctx->linelen)
{
log_info ("%s\n", ctx->line);
ctx->linelen = 0;
}
return;
}
while ((s = strchr (line, '\n')))
{
*s = 0;
if (ctx->line && ctx->linelen)
{
log_info ("%s", ctx->line);
ctx->linelen = 0;
log_printf ("%s\n", line);
}
else
log_info ("%s\n", line);
line = s + 1;
}
n = strlen (line);
if (n)
{
if (ctx->linelen + n + 1 >= ctx->linesize)
{
char *tmp;
size_t newsize;
newsize = ctx->linesize + ((n + 255) & ~255) + 1;
tmp = (ctx->line ? xtryrealloc (ctx->line, newsize)
: xtrymalloc (newsize));
if (!tmp)
{
log_error (_("error printing log line: %s\n"), strerror (errno));
return;
}
ctx->line = tmp;
ctx->linesize = newsize;
}
memcpy (ctx->line + ctx->linelen, line, n);
ctx->linelen += n;
ctx->line[ctx->linelen] = 0;
}
}
/* Read data from the log stream. Returns true if the log stream
* indicated EOF or error. */
static int
read_log_data (struct wrapper_context_s *ctx)
{
int rc;
size_t n;
char line[256];
rc = es_read (ctx->log_fp, line, sizeof line - 1, &n);
if (rc || !n) /* Error or EOF. */
{
if (rc)
{
gpg_error_t err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
return 0;
log_error (_("error reading log from ldap wrapper %d: %s\n"),
(int)ctx->pid, gpg_strerror (err));
}
print_log_line (ctx, NULL); /* Flush. */
SAFE_CLOSE (ctx->log_fp);
return 1;
}
line[n] = 0;
print_log_line (ctx, line);
if (ctx->stamp != (time_t)(-1))
ctx->stamp = time (NULL);
return 0;
}
/* This function is run by a separate thread to maintain the list of
wrappers and to log error messages from these wrappers. */
void *
ldap_reaper_thread (void *dummy)
{
gpg_error_t err;
struct wrapper_context_s *ctx;
struct wrapper_context_s *ctx_prev;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int millisecs;
gpgrt_poll_t *fparray = NULL;
int fparraysize = 0;
int count, i;
int ret;
time_t exptime;
(void)dummy;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
int any_action = 0;
/* Wait until we are needed and then setup the FPARRAY. */
/* Note: There is one unlock inside the block! */
lock_reaper_list ();
{
while (!reaper_list && !shutting_down)
{
if (npth_cond_wait (&reaper_run_cond, &reaper_list_mutex))
log_error ("ldap-reaper: waiting on condition failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
}
for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next)
if (ctx->log_fp)
count++;
if (count > fparraysize || !fparray)
{
/* Need to realloc the array. We simply discard it and
* replace it by a new one. */
xfree (fparray);
fparray = xtrycalloc (count? count : 1, sizeof *fparray);
if (!fparray)
{
err = gpg_error_from_syserror ();
log_error ("ldap-reaper can't allocate poll array: %s"
" - waiting 1s\n", gpg_strerror (err));
/* Note: Here we unlock and continue! */
unlock_reaper_list ();
npth_sleep (1);
continue;
}
fparraysize = count;
}
for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next)
{
if (ctx->log_fp)
{
log_assert (count < fparraysize);
fparray[count].stream = ctx->log_fp;
fparray[count].want_read = 1;
fparray[count].ignore = 0;
ctx->reaper_idx = count;
count++;
}
else
{
ctx->reaper_idx = -1;
fparray[count].ignore = 1;
}
}
for (i=count; i < fparraysize; i++)
fparray[i].ignore = 1;
}
unlock_reaper_list (); /* Note the one unlock inside the block. */
/* Compute the next timeout. */
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Inactivity is checked below. Nothing else to do. */
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
millisecs = timeout.tv_sec * 1000;
millisecs += timeout.tv_nsec / 1000000;
if (millisecs < 0)
millisecs = 1;
if (DBG_EXTPROG)
{
log_debug ("ldap-reaper: next run (count=%d size=%d timeout=%d)\n",
count, fparraysize, millisecs);
for (count=0; count < fparraysize; count++)
if (!fparray[count].ignore)
log_debug ("ldap-reaper: fp[%d] stream=%p %s\n",
count, fparray[count].stream,
fparray[count].want_read? "want_read":"");
}
ret = es_poll (fparray, fparraysize, millisecs);
if (ret < 0)
{
err = gpg_error_from_syserror ();
log_error ("ldap-reaper failed to poll: %s"
" - waiting 1s\n", gpg_strerror (err));
/* In case the reason for the error is a too large array, we
* release it so that it will be allocated smaller in the
* next round. */
xfree (fparray);
fparray = NULL;
fparraysize = 0;
npth_sleep (1);
continue;
}
if (DBG_EXTPROG)
{
for (count=0; count < fparraysize; count++)
if (!fparray[count].ignore)
log_debug ("ldap-reaper: fp[%d] stream=%p rc=%d %c%c%c%c%c%c%c\n",
count, fparray[count].stream, ret,
fparray[count].got_read? 'r':'-',
fparray[count].got_write?'w':'-',
fparray[count].got_oob? 'o':'-',
fparray[count].got_rdhup?'H':'-',
fparray[count].got_err? 'e':'-',
fparray[count].got_hup? 'h':'-',
fparray[count].got_nval? 'n':'-');
}
/* All timestamps before exptime should be considered expired. */
exptime = time (NULL);
if (exptime > INACTIVITY_TIMEOUT)
exptime -= INACTIVITY_TIMEOUT;
lock_reaper_list ();
{
for (ctx = reaper_list; ctx; ctx = ctx->next)
{
/* Check whether there is any logging to be done. We need
* to check FPARRAYSIZE because it can be 0 in case
* es_poll returned a timeout. */
if (fparraysize && ctx->log_fp && ctx->reaper_idx >= 0)
{
log_assert (ctx->reaper_idx < fparraysize);
if (fparray[ctx->reaper_idx].got_read)
{
if (read_log_data (ctx))
{
SAFE_CLOSE (ctx->log_fp);
any_action = 1;
}
}
}
/* Check whether the process is still running. */
if (ctx->pid != (pid_t)(-1))
{
int status;
err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0,
&status);
if (!err)
{
if (DBG_EXTPROG)
log_info (_("ldap wrapper %d ready"), (int)ctx->pid);
ctx->ready = 1;
gnupg_release_process (ctx->pid);
ctx->pid = (pid_t)(-1);
any_action = 1;
}
else if (gpg_err_code (err) == GPG_ERR_GENERAL)
{
if (status == 10)
log_info (_("ldap wrapper %d ready: timeout\n"),
(int)ctx->pid);
else
log_info (_("ldap wrapper %d ready: exitcode=%d\n"),
(int)ctx->pid, status);
ctx->ready = 1;
gnupg_release_process (ctx->pid);
ctx->pid = (pid_t)(-1);
any_action = 1;
}
else if (gpg_err_code (err) != GPG_ERR_TIMEOUT)
{
log_error (_("waiting for ldap wrapper %d failed: %s\n"),
(int)ctx->pid, gpg_strerror (err));
any_action = 1;
}
}
/* Check whether we should terminate the process. */
if (ctx->pid != (pid_t)(-1)
&& ctx->stamp != (time_t)(-1) && ctx->stamp < exptime)
{
gnupg_kill_process (ctx->pid);
ctx->stamp = (time_t)(-1);
log_info (_("ldap wrapper %d stalled - killing\n"),
(int)ctx->pid);
/* We need to close the log stream because the cleanup
* loop waits for it. */
SAFE_CLOSE (ctx->log_fp);
any_action = 1;
}
}
/* If something has been printed to the log file or we got an
* EOF from a wrapper, we now print the list of active
* wrappers. */
if (any_action && DBG_EXTPROG)
{
log_debug ("ldap worker states:\n");
for (ctx = reaper_list; ctx; ctx = ctx->next)
log_debug (" c=%p pid=%d/%d rdr=%p logfp=%p"
" ctrl=%p/%d la=%lu rdy=%d\n",
ctx,
(int)ctx->pid, (int)ctx->printable_pid,
ctx->reader, ctx->log_fp,
ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0,
(unsigned long)ctx->stamp, ctx->ready);
}
/* An extra loop to check whether ready marked wrappers may be
* removed. We may only do so if the ksba reader object is
* not anymore in use or we are in shutdown state. */
again:
for (ctx_prev=NULL, ctx=reaper_list; ctx; ctx_prev=ctx, ctx=ctx->next)
{
if (ctx->ready
&& ((!ctx->log_fp && !ctx->reader) || shutting_down))
{
if (ctx_prev)
ctx_prev->next = ctx->next;
else
reaper_list = ctx->next;
destroy_wrapper (ctx);
goto again;
}
}
}
unlock_reaper_list ();
}
/*NOTREACHED*/
return NULL; /* Make the compiler happy. */
}
/* Start the reaper thread for the ldap wrapper. */
void
ldap_reaper_launch_thread (void)
{
static int done;
npth_attr_t tattr;
npth_t thread;
int err;
if (done)
return;
done = 1;
#ifdef HAVE_W32_SYSTEM
/* Static init does not yet work in W32 nPth. */
if (npth_cond_init (&reaper_run_cond, NULL))
log_fatal ("%s: failed to init condition variabale: %s\n",
__func__, gpg_strerror (gpg_error_from_syserror ()));
#endif
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
if (npth_create (&thread, &tattr, ldap_reaper_thread, NULL))
{
err = gpg_error_from_syserror ();
log_error ("error spawning ldap reaper reaper thread: %s\n",
gpg_strerror (err) );
dirmngr_exit (1);
}
npth_setname_np (thread, "ldap-reaper");
npth_attr_destroy (&tattr);
}
/* Wait until all ldap wrappers have terminated. We assume that the
kill has already been sent to all of them. */
void
ldap_wrapper_wait_connections ()
{
lock_reaper_list ();
{
shutting_down = 1;
if (npth_cond_signal (&reaper_run_cond))
log_error ("%s: Ooops: signaling condition failed: %s\n",
__func__, gpg_strerror (gpg_error_from_syserror ()));
}
unlock_reaper_list ();
while (reaper_list)
npth_usleep (200);
}
/* This function is to be used to release a context associated with the
given reader object. */
void
ldap_wrapper_release_context (ksba_reader_t reader)
{
struct wrapper_context_s *ctx;
if (!reader )
return;
lock_reaper_list ();
{
for (ctx=reaper_list; ctx; ctx=ctx->next)
if (ctx->reader == reader)
{
if (DBG_EXTPROG)
log_debug ("releasing ldap worker c=%p pid=%d/%d rdr=%p"
" ctrl=%p/%d\n", ctx,
(int)ctx->pid, (int)ctx->printable_pid,
ctx->reader,
ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0);
ctx->reader = NULL;
SAFE_CLOSE (ctx->fp);
if (ctx->ctrl)
{
ctx->ctrl->refcount--;
ctx->ctrl = NULL;
}
if (ctx->fp_err)
log_info ("%s: reading from ldap wrapper %d failed: %s\n",
__func__, ctx->printable_pid, gpg_strerror (ctx->fp_err));
break;
}
}
unlock_reaper_list ();
}
/* Cleanup all resources held by the connection associated with
CTRL. This is used after a cancel to kill running wrappers. */
void
ldap_wrapper_connection_cleanup (ctrl_t ctrl)
{
struct wrapper_context_s *ctx;
lock_reaper_list ();
{
for (ctx=reaper_list; ctx; ctx=ctx->next)
if (ctx->ctrl && ctx->ctrl == ctrl)
{
ctx->ctrl->refcount--;
ctx->ctrl = NULL;
if (ctx->pid != (pid_t)(-1))
gnupg_kill_process (ctx->pid);
if (ctx->fp_err)
log_info ("%s: reading from ldap wrapper %d failed: %s\n",
__func__, ctx->printable_pid, gpg_strerror (ctx->fp_err));
}
}
unlock_reaper_list ();
}
/* This is the callback used by the ldap wrapper to feed the ksba
* reader with the wrapper's stdout. See the description of
* ksba_reader_set_cb for details. */
static int
reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread)
{
struct wrapper_context_s *ctx = cb_value;
size_t nleft = count;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int millisecs;
gpgrt_poll_t fparray[1];
int ret;
gpg_error_t err;
/* FIXME: We might want to add some internal buffering because the
ksba code does not do any buffering for itself (because a ksba
reader may be detached from another stream to read other data and
then it would be cumbersome to get back already buffered stuff). */
if (!buffer && !count && !nread)
return -1; /* Rewind is not supported. */
/* If we ever encountered a read error, don't continue (we don't want to
possibly overwrite the last error cause). Bail out also if the
file descriptor has been closed. */
if (ctx->fp_err || !ctx->fp)
{
*nread = 0;
return -1;
}
memset (fparray, 0, sizeof fparray);
fparray[0].stream = ctx->fp;
fparray[0].want_read = 1;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
while (nleft > 0)
{
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
err = dirmngr_tick (ctx->ctrl);
if (err)
{
ctx->fp_err = err;
SAFE_CLOSE (ctx->fp);
return -1;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
millisecs = timeout.tv_sec * 1000;
millisecs += timeout.tv_nsec / 1000000;
if (millisecs < 0)
millisecs = 1;
if (DBG_EXTPROG)
{
log_debug ("%s: fp[0] stream=%p %s\n",
__func__, fparray[0].stream,
fparray[0].want_read?"want_read":"");
}
ret = es_poll (fparray, DIM (fparray), millisecs);
if (ret < 0)
{
ctx->fp_err = gpg_error_from_syserror ();
log_error ("error polling stdout of ldap wrapper %d: %s\n",
ctx->printable_pid, gpg_strerror (ctx->fp_err));
SAFE_CLOSE (ctx->fp);
return -1;
}
if (DBG_EXTPROG)
{
log_debug ("%s: fp[0] stream=%p rc=%d %c%c%c%c%c%c%c\n",
__func__, fparray[0].stream, ret,
fparray[0].got_read? 'r':'-',
fparray[0].got_write?'w':'-',
fparray[0].got_oob? 'o':'-',
fparray[0].got_rdhup?'H':'-',
fparray[0].got_err? 'e':'-',
fparray[0].got_hup? 'h':'-',
fparray[0].got_nval? 'n':'-');
}
if (!ret)
{
/* Timeout. Will be handled when calculating the next timeout. */
continue;
}
if (fparray[0].got_read)
{
size_t n;
if (es_read (ctx->fp, buffer, nleft, &n))
{
ctx->fp_err = gpg_error_from_syserror ();
if (gpg_err_code (ctx->fp_err) == GPG_ERR_EAGAIN)
ctx->fp_err = 0;
else
{
log_error ("%s: error reading: %s (%d)\n",
__func__, gpg_strerror (ctx->fp_err), ctx->fp_err);
SAFE_CLOSE (ctx->fp);
return -1;
}
}
else if (!n) /* EOF */
{
if (nleft == count)
return -1; /* EOF. */
break;
}
nleft -= n;
buffer += n;
if (n > 0 && ctx->stamp != (time_t)(-1))
ctx->stamp = time (NULL);
}
}
*nread = count - nleft;
return 0;
}
/* Fork and exec the LDAP wrapper and return a new libksba reader
object at READER. ARGV is a NULL terminated list of arguments for
the wrapper. The function returns 0 on success or an error code.
Special hack to avoid passing a password through the command line
which is globally visible: If the first element of ARGV is "--pass"
it will be removed and instead the environment variable
DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern
OSes the environment is not visible to other users. For those old
systems where it can't be avoided, we don't want to go into the
hassle of passing the password via stdin; it's just too complicated
and an LDAP password used for public directory lookups should not
be that confidential. */
gpg_error_t
ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
{
gpg_error_t err;
pid_t pid;
struct wrapper_context_s *ctx;
int i;
int j;
const char **arg_list;
const char *pgmname;
estream_t outfp, errfp;
/* It would be too simple to connect stderr just to our logging
stream. The problem is that if we are running multi-threaded
everything gets intermixed. Clearly we don't want this. So the
only viable solutions are either to have another thread
responsible for logging the messages or to add an option to the
wrapper module to do the logging on its own. Given that we anyway
need a way to reap the child process and this is best done using a
general reaping thread, that thread can do the logging too. */
ldap_reaper_launch_thread ();
*reader = NULL;
/* Files: We need to prepare stdin and stdout. We get stderr from
the function. */
if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program)
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP);
else
pgmname = opt.ldap_wrapper_program;
/* Create command line argument array. */
for (i = 0; argv[i]; i++)
;
arg_list = xtrycalloc (i + 2, sizeof *arg_list);
if (!arg_list)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating memory: %s\n"), strerror (errno));
return err;
}
for (i = j = 0; argv[i]; i++, j++)
if (!i && argv[i + 1] && !strcmp (*argv, "--pass"))
{
arg_list[j] = "--env-pass";
setenv ("DIRMNGR_LDAP_PASS", argv[1], 1);
i++;
}
else
arg_list[j] = (char*) argv[i];
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating memory: %s\n"), strerror (errno));
xfree (arg_list);
return err;
}
err = gnupg_spawn_process (pgmname, arg_list,
NULL, NULL, GNUPG_SPAWN_NONBLOCK,
NULL, &outfp, &errfp, &pid);
xfree (arg_list);
if (err)
{
xfree (ctx);
log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
return err;
}
ctx->pid = pid;
ctx->printable_pid = (int) pid;
ctx->fp = outfp;
ctx->log_fp = errfp;
ctx->ctrl = ctrl;
ctrl->refcount++;
ctx->stamp = time (NULL);
err = ksba_reader_new (reader);
if (!err)
err = ksba_reader_set_cb (*reader, reader_callback, ctx);
if (err)
{
log_error (_("error initializing reader object: %s\n"),
gpg_strerror (err));
destroy_wrapper (ctx);
ksba_reader_release (*reader);
*reader = NULL;
return err;
}
/* Hook the context into our list of running wrappers. */
lock_reaper_list ();
{
ctx->reader = *reader;
ctx->next = reaper_list;
reaper_list = ctx;
if (npth_cond_signal (&reaper_run_cond))
log_error ("ldap-wrapper: Ooops: signaling condition failed: %s (%d)\n",
gpg_strerror (gpg_error_from_syserror ()), errno);
}
unlock_reaper_list ();
if (DBG_EXTPROG)
log_debug ("ldap wrapper %d started (%p, %s)\n",
(int)ctx->pid, ctx->reader, pgmname);
/* Need to wait for the first byte so we are able to detect an empty
output and not let the consumer see an EOF without further error
indications. The CRL loading logic assumes that after return
from this function, a failed search (e.g. host not found ) is
indicated right away. */
{
unsigned char c;
err = read_buffer (*reader, &c, 1);
if (err)
{
ldap_wrapper_release_context (*reader);
ksba_reader_release (*reader);
*reader = NULL;
if (gpg_err_code (err) == GPG_ERR_EOF)
return gpg_error (GPG_ERR_NO_DATA);
else
return err;
}
ksba_reader_unread (*reader, &c, 1);
}
return 0;
}
diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c
index e19779c59..0b846651c 100644
--- a/dirmngr/ocsp.c
+++ b/dirmngr/ocsp.c
@@ -1,889 +1,889 @@
/* ocsp.c - OCSP management
* Copyright (C) 2004, 2007 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
#include "http.h"
#include "validate.h"
#include "certcache.h"
#include "ocsp.h"
/* The maximum size we allow as a response from an OCSP reponder. */
#define MAX_RESPONSE_SIZE 65536
static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1";
/* Telesec attribute used to implement a positive confirmation.
CertHash ::= SEQUENCE {
HashAlgorithm AlgorithmIdentifier,
certificateHash OCTET STRING }
*/
/* static const char oidstr_certHash[] = "1.3.36.8.3.13"; */
/* Read from FP and return a newly allocated buffer in R_BUFFER with the
entire data read from FP. */
static gpg_error_t
read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
unsigned char *buffer;
size_t bufsize, nbytes;
*r_buffer = NULL;
*r_buflen = 0;
bufsize = 4096;
buffer = xtrymalloc (bufsize);
if (!buffer)
return gpg_error_from_errno (errno);
nbytes = 0;
for (;;)
{
unsigned char *tmp;
size_t nread = 0;
assert (nbytes < bufsize);
nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp);
if (nread < bufsize-nbytes && es_ferror (fp))
{
err = gpg_error_from_errno (errno);
log_error (_("error reading from responder: %s\n"),
strerror (errno));
xfree (buffer);
return err;
}
if ( !(nread == bufsize-nbytes && !es_feof (fp)))
{ /* Response successfully received. */
nbytes += nread;
*r_buffer = buffer;
*r_buflen = nbytes;
return 0;
}
nbytes += nread;
/* Need to enlarge the buffer. */
if (bufsize >= MAX_RESPONSE_SIZE)
{
log_error (_("response from server too large; limit is %d bytes\n"),
MAX_RESPONSE_SIZE);
xfree (buffer);
return gpg_error (GPG_ERR_TOO_LARGE);
}
bufsize += 4096;
tmp = xtryrealloc (buffer, bufsize);
if (!tmp)
{
err = gpg_error_from_errno (errno);
xfree (buffer);
return err;
}
buffer = tmp;
}
}
/* Construct an OCSP request, send it to the configured OCSP responder
and parse the response. On success the OCSP context may be used to
further process the response. The signature value and the
production date are returned at R_SIGVAL and R_PRODUCED_AT; they
may be NULL or an empty string if not available. A new hash
context is returned at R_MD. */
static gpg_error_t
do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp,
const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert,
ksba_sexp_t *r_sigval, ksba_isotime_t r_produced_at,
gcry_md_hd_t *r_md)
{
gpg_error_t err;
unsigned char *request, *response;
size_t requestlen, responselen;
http_t http;
ksba_ocsp_response_status_t response_status;
const char *t;
int redirects_left = 2;
char *free_this = NULL;
(void)ctrl;
*r_sigval = NULL;
*r_produced_at = 0;
*r_md = NULL;
if (dirmngr_use_tor ())
{
/* For now we do not allow OCSP via Tor due to possible privacy
concerns. Needs further research. */
log_error (_("OCSP request not possible due to Tor mode\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (opt.disable_http)
{
log_error (_("OCSP request not possible due to disabled HTTP\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
err = ksba_ocsp_add_target (ocsp, cert, issuer_cert);
if (err)
{
log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err));
return err;
}
{
size_t n;
unsigned char nonce[32];
n = ksba_ocsp_set_nonce (ocsp, NULL, 0);
if (n > sizeof nonce)
n = sizeof nonce;
gcry_create_nonce (nonce, n);
ksba_ocsp_set_nonce (ocsp, nonce, n);
}
err = ksba_ocsp_build_request (ocsp, &request, &requestlen);
if (err)
{
log_error (_("error building OCSP request: %s\n"), gpg_strerror (err));
return err;
}
once_more:
err = http_open (ctrl, &http, HTTP_REQ_POST, url, NULL, NULL,
((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
| (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
| (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)
| (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)),
ctrl->http_proxy, NULL, NULL, NULL);
if (err)
{
log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err));
xfree (free_this);
return err;
}
es_fprintf (http_get_write_ptr (http),
"Content-Type: application/ocsp-request\r\n"
"Content-Length: %lu\r\n",
(unsigned long)requestlen );
http_start_data (http);
if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1)
{
err = gpg_error_from_errno (errno);
log_error ("error sending request to '%s': %s\n", url, strerror (errno));
http_close (http, 0);
xfree (request);
xfree (free_this);
return err;
}
xfree (request);
request = NULL;
err = http_wait_response (http);
if (err || http_get_status_code (http) != 200)
{
if (err)
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
else
{
switch (http_get_status_code (http))
{
case 301:
case 302:
{
const char *s = http_get_header (http, "Location");
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
url, s?s:"[none]", http_get_status_code (http));
if (s && *s && redirects_left-- )
{
xfree (free_this); url = NULL;
free_this = xtrystrdup (s);
if (!free_this)
err = gpg_error_from_errno (errno);
else
{
url = free_this;
http_close (http, 0);
goto once_more;
}
}
else
err = gpg_error (GPG_ERR_NO_DATA);
log_error (_("too many redirections\n"));
}
break;
case 413: /* Payload too large */
err = gpg_error (GPG_ERR_TOO_LARGE);
break;
default:
log_error (_("error accessing '%s': http status %u\n"),
url, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
break;
}
}
http_close (http, 0);
xfree (free_this);
return err;
}
err = read_response (http_get_read_ptr (http), &response, &responselen);
http_close (http, 0);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (free_this);
return err;
}
/* log_printhex (response, responselen, "ocsp response"); */
err = ksba_ocsp_parse_response (ocsp, response, responselen,
&response_status);
if (err)
{
log_error (_("error parsing OCSP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (response);
xfree (free_this);
return err;
}
switch (response_status)
{
case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break;
case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break;
case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break;
case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break;
case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break;
case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break;
case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break;
case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break;
case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break;
default: t = "[unknown status]"; break;
}
if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS)
{
int hash_algo;
if (opt.verbose)
log_info (_("OCSP responder at '%s' status: %s\n"), url, t);
- /* Get the signature value now because we can all this fucntion
+ /* Get the signature value now because we can all this function
* only once. */
*r_sigval = ksba_ocsp_get_sig_val (ocsp, r_produced_at);
hash_algo = hash_algo_from_sigval (*r_sigval);
if (!hash_algo)
{
if (opt.verbose)
log_info ("ocsp: using SHA-256 as fallback hash algo.\n");
hash_algo = GCRY_MD_SHA256;
}
err = gcry_md_open (r_md, hash_algo, 0);
if (err)
{
log_error (_("failed to establish a hashing context for OCSP: %s\n"),
gpg_strerror (err));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (*r_md, "ocsp");
err = ksba_ocsp_hash_response (ocsp, response, responselen,
HASH_FNC, *r_md);
if (err)
log_error (_("hashing the OCSP response for '%s' failed: %s\n"),
url, gpg_strerror (err));
}
else
{
log_error (_("OCSP responder at '%s' status: %s\n"), url, t);
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (response);
xfree (free_this);
if (err)
{
xfree (*r_sigval);
*r_sigval = NULL;
*r_produced_at = 0;
gcry_md_close (*r_md);
*r_md = NULL;
}
return err;
}
/* Validate that CERT is indeed valid to sign an OCSP response. If
SIGNER_FPR_LIST is not NULL we simply check that CERT matches one
of the fingerprints in this list. */
static gpg_error_t
validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
char *fpr;
if (signer_fpr_list)
{
fpr = get_fingerprint_hexstring (cert);
for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr);
signer_fpr_list = signer_fpr_list->next)
;
if (signer_fpr_list)
err = 0;
else
{
log_error (_("not signed by a default OCSP signer's certificate"));
err = gpg_error (GPG_ERR_BAD_CA_CERT);
}
xfree (fpr);
}
else
{
/* We avoid duplicating the entire certificate validation code
from gpgsm here. Because we have no way calling back to the
client and letting it compute the validity, we use the ugly
hack of telling the client that the response will only be
valid if the certificate given in this status message is
valid.
Note, that in theory we could simply ask the client via an
inquire to validate a certificate but this might involve
calling DirMngr again recursively - we can't do that as of now
(neither DirMngr nor gpgsm have the ability for concurrent
access to DirMngr. */
/* FIXME: We should cache this certificate locally, so that the next
call to dirmngr won't need to look it up - if this works at
all. */
fpr = get_fingerprint_hexstring (cert);
dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL);
xfree (fpr);
err = 0;
}
return err;
}
/* Helper for check_signature. */
static int
check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig,
gcry_sexp_t s_hash, fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
ksba_sexp_t pubkey;
gcry_sexp_t s_pkey = NULL;
pubkey = ksba_cert_get_public_key (cert);
if (!pubkey)
err = gpg_error (GPG_ERR_INV_OBJ);
else
err = canon_sexp_to_gcry (pubkey, &s_pkey);
xfree (pubkey);
if (!err)
err = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (!err)
err = validate_responder_cert (ctrl, cert, signer_fpr_list);
if (!err)
{
gcry_sexp_release (s_pkey);
return 0; /* Successfully verified the signature. */
}
/* We simply ignore all errors. */
gcry_sexp_release (s_pkey);
return err;
}
/* Check the signature of an OCSP response. OCSP is the context,
S_SIG the signature value and MD the handle of the hash we used for
the response. This function automagically finds the correct public
key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been
used and thus the certificate is one of those identified by
the fingerprints. */
static gpg_error_t
check_signature (ctrl_t ctrl,
ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
int algo, cert_idx;
gcry_sexp_t s_hash;
ksba_cert_t cert;
const char *s;
/* Create a suitable S-expression with the hash value of our response. */
gcry_md_final (md);
algo = gcry_md_get_algo (md);
s = gcry_md_algo_name (algo);
if (algo && s && strlen (s) < 16)
{
char hashalgostr[16+1];
int i;
for (i=0; s[i]; i++)
hashalgostr[i] = ascii_tolower (s[i]);
hashalgostr[i] = 0;
err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
hashalgostr,
(int)gcry_md_get_algo_dlen (algo),
gcry_md_read (md, algo));
}
else
err = gpg_error (GPG_ERR_DIGEST_ALGO);
if (err)
{
log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
return err;
}
/* Get rid of old OCSP specific certificate references. */
release_ctrl_ocsp_certs (ctrl);
if (signer_fpr_list && !signer_fpr_list->next)
{
/* There is exactly one signer fingerprint given. Thus we use
the default OCSP responder's certificate and instantly know
the certificate to use. */
cert = get_cert_byhexfpr (signer_fpr_list->hexfpr);
if (!cert)
cert = get_cert_local (ctrl, signer_fpr_list->hexfpr);
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, s_hash,
signer_fpr_list);
ksba_cert_release (cert);
cert = NULL;
if (!err)
{
gcry_sexp_release (s_hash);
return 0; /* Successfully verified the signature. */
}
}
}
else
{
char *name;
ksba_sexp_t keyid;
/* Put all certificates included in the response into the cache
and setup a list of those certificate which will later be
preferred used when locating certificates. */
for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx));
cert_idx++)
{
cert_ref_t cref;
/* dump_cert ("from ocsp response", cert); */
cref = xtrymalloc (sizeof *cref);
if (!cref)
log_error (_("allocating list item failed: %s\n"),
gcry_strerror (err));
else if (!cache_cert_silent (cert, &cref->fpr))
{
cref->next = ctrl->ocsp_certs;
ctrl->ocsp_certs = cref;
}
else
xfree (cref);
}
/* Get the certificate by means of the responder ID. */
err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid);
if (err)
{
log_error (_("error getting responder ID: %s\n"),
gcry_strerror (err));
return err;
}
cert = find_cert_bysubject (ctrl, name, keyid);
if (!cert)
{
log_error ("responder certificate ");
if (name)
log_printf ("'/%s' ", name);
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
log_printf ("not found\n");
}
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, s_hash,
signer_fpr_list);
ksba_cert_release (cert);
if (!err)
{
ksba_free (name);
ksba_free (keyid);
gcry_sexp_release (s_hash);
return 0; /* Successfully verified the signature. */
}
log_error ("responder certificate ");
if (name)
log_printf ("'/%s' ", name);
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
log_printf ("did not verify: %s\n", gpg_strerror (err));
}
ksba_free (name);
ksba_free (keyid);
}
gcry_sexp_release (s_hash);
log_error (_("no suitable certificate found to verify the OCSP response\n"));
return gpg_error (GPG_ERR_NO_PUBKEY);
}
/* Check whether the certificate either given by fingerprint CERT_FPR
or directly through the CERT object is valid by running an OCSP
transaction. With FORCE_DEFAULT_RESPONDER set only the configured
default responder is used. */
gpg_error_t
ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr,
int force_default_responder)
{
gpg_error_t err;
ksba_ocsp_t ocsp = NULL;
ksba_cert_t issuer_cert = NULL;
ksba_sexp_t sigval = NULL;
gcry_sexp_t s_sig = NULL;
ksba_isotime_t current_time;
ksba_isotime_t this_update, next_update, revocation_time, produced_at;
ksba_isotime_t tmp_time;
ksba_status_t status;
ksba_crl_reason_t reason;
char *url_buffer = NULL;
const char *url;
gcry_md_hd_t md = NULL;
int i, idx;
char *oid;
ksba_name_t name;
fingerprint_list_t default_signer = NULL;
/* Get the certificate. */
if (cert)
{
ksba_cert_ref (cert);
err = find_issuing_cert (ctrl, cert, &issuer_cert);
if (err)
{
log_error (_("issuer certificate not found: %s\n"),
gpg_strerror (err));
goto leave;
}
}
else
{
cert = get_cert_local (ctrl, cert_fpr);
if (!cert)
{
log_error (_("caller did not return the target certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer_cert = get_issuing_cert_local (ctrl, NULL);
if (!issuer_cert)
{
log_error (_("caller did not return the issuing certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
/* Create an OCSP instance. */
err = ksba_ocsp_new (&ocsp);
if (err)
{
log_error (_("failed to allocate OCSP context: %s\n"),
gpg_strerror (err));
goto leave;
}
/* Figure out the OCSP responder to use.
1. Try to get the reponder from the certificate.
We do only take http and https style URIs into account.
2. If this fails use the default responder, if any.
*/
url = NULL;
for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder
&& !(err=ksba_cert_get_authority_info_access (cert, idx,
&oid, &name)); idx++)
{
if ( !strcmp (oid, oidstr_ocsp) )
{
for (i=0; !url && ksba_name_enum (name, i); i++)
{
char *p = ksba_name_get_uri (name, i);
if (p && (!ascii_strncasecmp (p, "http:", 5)
|| !ascii_strncasecmp (p, "https:", 6)))
url = url_buffer = p;
else
xfree (p);
}
}
ksba_name_release (name);
ksba_free (oid);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err));
goto leave;
}
if (!url)
{
if (!opt.ocsp_responder || !*opt.ocsp_responder)
{
log_info (_("no default OCSP responder defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
if (!opt.ocsp_signer)
{
log_info (_("no default OCSP signer defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
url = opt.ocsp_responder;
default_signer = opt.ocsp_signer;
if (opt.verbose)
log_info (_("using default OCSP responder '%s'\n"), url);
}
else
{
if (opt.verbose)
log_info (_("using OCSP responder '%s'\n"), url);
}
/* Ask the OCSP responder. */
err = do_ocsp_request (ctrl, ocsp, url, cert, issuer_cert,
&sigval, produced_at, &md);
if (err)
goto leave;
/* It is sometimes useful to know the responder ID. */
if (opt.verbose)
{
char *resp_name;
ksba_sexp_t resp_keyid;
err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid);
if (err)
log_info (_("error getting responder ID: %s\n"), gpg_strerror (err));
else
{
log_info ("responder id: ");
if (resp_name)
log_printf ("'/%s' ", resp_name);
if (resp_keyid)
{
log_printf ("{");
dump_serial (resp_keyid);
log_printf ("} ");
}
log_printf ("\n");
}
ksba_free (resp_name);
ksba_free (resp_keyid);
err = 0;
}
/* We got a useful answer, check that the answer has a valid signature. */
if (!sigval || !*produced_at || !md)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) )
goto leave;
xfree (sigval);
sigval = NULL;
err = check_signature (ctrl, ocsp, s_sig, md, default_signer);
if (err)
goto leave;
/* We only support one certificate per request. Check that the
answer matches the right certificate. */
err = ksba_ocsp_get_status (ocsp, cert,
&status, this_update, next_update,
revocation_time, &reason);
if (err)
{
log_error (_("error getting OCSP status for target certificate: %s\n"),
gpg_strerror (err));
goto leave;
}
/* In case the certificate has been revoked, we better invalidate
our cached validation status. */
if (status == KSBA_STATUS_REVOKED)
{
time_t validated_at = 0; /* That is: No cached validation available. */
err = ksba_cert_set_user_data (cert, "validated_at",
&validated_at, sizeof (validated_at));
if (err)
{
log_error ("set_user_data(validated_at) failed: %s\n",
gpg_strerror (err));
err = 0; /* The certificate is anyway revoked, and that is a
more important message than the failure of our
cache. */
}
}
if (opt.verbose)
{
log_info (_("certificate status is: %s (this=%s next=%s)\n"),
status == KSBA_STATUS_GOOD? _("good"):
status == KSBA_STATUS_REVOKED? _("revoked"):
status == KSBA_STATUS_UNKNOWN? _("unknown"):
status == KSBA_STATUS_NONE? _("none"): "?",
this_update, next_update);
if (status == KSBA_STATUS_REVOKED)
log_info (_("certificate has been revoked at: %s due to: %s\n"),
revocation_time,
reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified":
reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise":
reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise":
reason == KSBA_CRLREASON_AFFILIATION_CHANGED?
"affiliation changed":
reason == KSBA_CRLREASON_SUPERSEDED? "superseded":
reason == KSBA_CRLREASON_CESSATION_OF_OPERATION?
"cessation of operation":
reason == KSBA_CRLREASON_CERTIFICATE_HOLD?
"certificate on hold":
reason == KSBA_CRLREASON_REMOVE_FROM_CRL?
"removed from CRL":
reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN?
"privilege withdrawn":
reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise":
reason == KSBA_CRLREASON_OTHER? "other":"?");
}
if (status == KSBA_STATUS_REVOKED)
err = gpg_error (GPG_ERR_CERT_REVOKED);
else if (status == KSBA_STATUS_UNKNOWN)
err = gpg_error (GPG_ERR_NO_DATA);
else if (status != KSBA_STATUS_GOOD)
err = gpg_error (GPG_ERR_GENERAL);
/* Allow for some clock skew. */
gnupg_get_isotime (current_time);
add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew);
if (strcmp (this_update, current_time) > 0 )
{
log_error (_("OCSP responder returned a status in the future\n"));
log_info ("used now: %s this_update: %s\n", current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that THIS_UPDATE is not too far back in the past. */
gnupg_copy_time (tmp_time, this_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_max_period+opt.ocsp_max_clock_skew);
if (!*tmp_time || strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned a non-current status\n"));
log_info ("used now: %s this_update: %s\n",
current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that we are not beyond NEXT_UPDATE (plus some extra time). */
if (*next_update)
{
gnupg_copy_time (tmp_time, next_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_current_period+opt.ocsp_max_clock_skew);
if (!*tmp_time && strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned an too old status\n"));
log_info ("used now: %s next_update: %s\n",
current_time, next_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
}
leave:
gcry_md_close (md);
gcry_sexp_release (s_sig);
xfree (sigval);
ksba_cert_release (issuer_cert);
ksba_cert_release (cert);
ksba_ocsp_release (ocsp);
xfree (url_buffer);
return err;
}
/* Release the list of OCSP certificates hold in the CTRL object. */
void
release_ctrl_ocsp_certs (ctrl_t ctrl)
{
while (ctrl->ocsp_certs)
{
cert_ref_t tmp = ctrl->ocsp_certs->next;
xfree (ctrl->ocsp_certs);
ctrl->ocsp_certs = tmp;
}
}
diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c
index a47cdebc8..2974f5d08 100644
--- a/dirmngr/workqueue.c
+++ b/dirmngr/workqueue.c
@@ -1,214 +1,214 @@
/* workqueue.c - Maintain a queue of background tasks
* Copyright (C) 2017 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include "dirmngr.h"
/* An object for one item in the workqueue. */
struct wqitem_s
{
struct wqitem_s *next;
/* This flag is set if the task requires network access. */
unsigned int need_network:1;
/* The id of the session which created this task. If this is 0 the
* task is not associated with a specific session. */
unsigned int session_id;
- /* The function to perform the backgrount task. */
+ /* The function to perform the background task. */
wqtask_t func;
/* A string with the string argument for that task. */
char args[1];
};
typedef struct wqitem_s *wqitem_t;
/* The workque is a simple linked list. */
static wqitem_t workqueue;
/* Dump the queue using Assuan status comments. */
void
workqueue_dump_queue (ctrl_t ctrl)
{
wqitem_t saved_workqueue;
wqitem_t item;
unsigned int count;
- /* Temporay detach the entiere workqueue so that other threads don't
+ /* Temporarily detach the entiere workqueue so that other threads don't
* get into our way. */
saved_workqueue = workqueue;
workqueue = NULL;
for (count=0, item = saved_workqueue; item; item = item->next)
count++;
dirmngr_status_helpf (ctrl, "wq: number of entries: %u", count);
for (item = saved_workqueue; item; item = item->next)
dirmngr_status_helpf (ctrl, "wq: sess=%u net=%d %s(\"%.100s%s\")",
item->session_id, item->need_network,
item->func? item->func (NULL, NULL): "nop",
item->args, strlen (item->args) > 100? "[...]":"");
/* Restore then workqueue. Actually we append the saved queue do a
* possibly updated workqueue. */
if (!(item=workqueue))
workqueue = saved_workqueue;
else
{
while (item->next)
item = item->next;
item->next = saved_workqueue;
}
}
/* Append the task (FUNC,ARGS) to the work queue. FUNC shall return
* its name when called with (NULL, NULL). */
gpg_error_t
workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id,
int need_network)
{
wqitem_t item, wi;
item = xtrycalloc (1, sizeof *item + strlen (args));
if (!item)
return gpg_error_from_syserror ();
strcpy (item->args, args);
item->func = func;
item->session_id = session_id;
item->need_network = !!need_network;
if (!(wi=workqueue))
workqueue = item;
else
{
while (wi->next)
wi = wi->next;
wi->next = item;
}
return 0;
}
/* Run the task described by ITEM. ITEM must have been detached from
* the workqueue; its ownership is transferred to this function. */
static void
run_a_task (ctrl_t ctrl, wqitem_t item)
{
log_assert (!item->next);
if (opt.verbose)
log_info ("session %u: running %s(\"%s%s\")\n",
item->session_id,
item->func? item->func (NULL, NULL): "nop",
item->args, strlen (item->args) > 100? "[...]":"");
if (item->func)
item->func (ctrl, item->args);
xfree (item);
}
/* Run tasks not associated with a session. This is called from the
* ticker every few minutes. If WITH_NETWORK is not set tasks which
* require the network are not run. */
void
workqueue_run_global_tasks (ctrl_t ctrl, int with_network)
{
wqitem_t item, prev;
with_network = !!with_network;
if (opt.verbose)
log_info ("running scheduled tasks%s\n", with_network?" (with network)":"");
for (;;)
{
prev = NULL;
for (item = workqueue; item; prev = item, item = item->next)
if (!item->session_id
&& (!item->need_network || (item->need_network && with_network)))
break;
if (!item)
break; /* No more tasks to run. */
/* Detach that item from the workqueue. */
if (!prev)
workqueue = item->next;
else
prev->next = item->next;
item->next = NULL;
/* Run the task. */
run_a_task (ctrl, item);
}
}
/* Run tasks scheduled for running after a session. Those tasks are
* identified by the SESSION_ID. */
void
workqueue_run_post_session_tasks (unsigned int session_id)
{
struct server_control_s ctrlbuf;
ctrl_t ctrl = NULL;
wqitem_t item, prev;
if (!session_id)
return;
for (;;)
{
prev = NULL;
for (item = workqueue; item; prev = item, item = item->next)
if (item->session_id == session_id)
break;
if (!item)
break; /* No more tasks for this session. */
/* Detach that item from the workqueue. */
if (!prev)
workqueue = item->next;
else
prev->next = item->next;
item->next = NULL;
/* Create a CTRL object the first time we need it. */
if (!ctrl)
{
memset (&ctrlbuf, 0, sizeof ctrlbuf);
ctrl = &ctrlbuf;
dirmngr_init_default_ctrl (ctrl);
}
/* Run the task. */
run_a_task (ctrl, item);
}
dirmngr_deinit_default_ctrl (ctrl);
}
diff --git a/doc/Notes b/doc/Notes
index 33ef29278..d420ff0ac 100644
--- a/doc/Notes
+++ b/doc/Notes
@@ -1,245 +1,245 @@
Add an info page for watchgnupg.
> * How to mark a CA certificate as trusted.
There are two ways:
1. Let gpg-agent do this for you. Since version 1.9.9 you need to
add the option --allow-mark-trusted gpg-agent.conf or when
invoking gpg-agent. Every time gpgsm notices an untrusted root
certificate gpg-agent will pop up a dialog to ask whether this
certificate should be trusted. This is similar to whatmost
browsers do.
The disadvantage of this method and the reason why
--allow-mark-trusted is required is that the list of trusted root
certificates will grow, because almost all user will just hit
"yes, I trust" and "yes, I verified the fingerprint" without
understanding that this is a very serious decision.
2. Use your editor. Edit the file ~/.gnupg/trustlist.txt and add
the fingerprints of the trusted root certificates. There are
comments on the top explaining the simple format. The current
CVS version allows for colons in the fingerprint, so you can
easily cut and paste it from wherever you know that this is the
correct fingerprint.
An example for an entry in the trustlist.txt is:
# CN=PCA-1-Verwaltung,O=PKI-1-Verwaltung,C=de
3EEE3D8BB7F0FE5C9F5804A3A7E51BCE98209DF9 S
This is in fact one that probably made its way into the file using the
first method. As usual a # indicates a comment. The trailing S means
that this is to be used for (X.509).
It is not possible to trust intermediate CA certificates; gpgsm always
checks the entire chain of certificates.
> * How to import a key and bind it to some certificate already
> imported. Alternatively, import key and certificate together, from
> a pkcs12 blob, or pkcs8 + certificate blobs, or whatever.
> Alternatively, don't import the key at all, but specify location of
> key using a parameter when signing.
You always need to import the key; there is something similar to a
keyring (here called a keybox: ~/.gnupg/pubring.kbx).
Importing a key either from a binary or ascii armored (PEM) certificate
file or from a cert-only signature file is done using
gpg --import FILE
or
gpg --import < FILE
In general you should first import the root certificates and then down
to the end user certificate. You may put all into one file and gpgsm
will do the right thing in this case independent of the order.
While verifying a signature, all included certificates are
automagically imported.
To import from a pkcs#12 file you may use the same command; if a
private key is contained in that file, you will be asked for the
transport passphrases as well as for the new passphrase used to
protect it in gpg-agent's private key storage
(~/.gnupg/private-keys-v1.d/). Note that the pkcs#12 support is very
basic but sufficient for certificates exported from Mozilla, OpenSSL
and MS Outlook.
Background info on private keys:
If you want to look at the private key you first need to know the name
of the keyfile. Run the command "gpgsm -K --with-key-data [KEYID]" and
you get an output like:
crs::1024:1:CF8[..]6D:20040105T184908:2006[...]:09::CN=ZS[....]::esES:
fpr:::::::::3B50BF2BDAF2[...]1AE6796D:::2812[...]508F21F065E65E44:
grp:::::::::C92DB9CFD588ADE846BE3AC4E7A2E1B11A4A2ADB:
uid:::::::::CN=Werner Koch,OU=test,O=g10 Code,C=de::
uid:::::::::<wk@g10code.de>::
This should be familiar to advanced gpg-users; see doc/DETAILS in gpg
1.3 (CVS HEAD) for a description of the records. The value in the
"grp" tagged record is the so called keygrip and you should find a
file ~/.gnupg/private-keys-v1.d/C92DB9CFD588ADE846BE3AC4E7A2E1B11A4A2ADB.key
with the private and public key in an S-expression like format. The
gpg-protect-tool may be used to display it in a human readable format:
$ gpgsm --call-protect-tool ~/.gnupg/private-keys-v1.d/C9[...]B.key
(protected-private-key
(rsa
(n #00C16B6E807C47BB[...]10487#)
(e #010001#)
(protected openpgp-s2k3-sha1-aes-cbc
(
(sha1 "Hvü9Qt^Ç" "96")
#2B17DC766AEA2568EE0C688E18F9757E#)
#65A4FF9F30750A1300[...]7#)
)
)
The current CVS version of gpgsm has a command --dump-keys which lists
more details of a key including the keygrip so you don't need to use
the colon format if you want to manually debug things.
$ gpgsm --dump-keys
Serial number: 01
Issuer: CN=Trust Anchor,O=Test Certificates,C=US
Subject: CN=Trust Anchor,O=Test Certificates,C=US
sha1_fpr: 66:8A:47:56:A2:DC:88:FF:DA:B8:95:E1:3C:63:37:55:5F:0A:F7:BF
md5_fpr: 03:01:3B:BB:EC:6C:5D:48:88:4C:95:63:99:84:ED:C0
keygrip: 6A082B3063F6DA6D68B2994AB11B4328FD6206D2
notBefore: 2001-04-19 14:57:20
notAfter: 2011-04-19 14:57:20
hashAlgo: 1.2.840.113549.1.1.5 (sha1WithRSAEncryption)
keyType: 1024 bit RSA
authKeyId: [none]
keyUsage: certSign crlSign
extKeyUsage: [none]
policies: [none]
chainLength: unlimited
crlDP: [none]
authInfo: [none]
subjInfo: [none]
extn: 2.5.29.14 (subjectKeyIdentifier) [22 octets]
> * How to import a CRL
CRLs are managed by the dirmngr which is a separate package. The idea
-is to eventaully turn it into a system daemon, so that on a multi-user
+is to eventually turn it into a system daemon, so that on a multi-user
machine CRLs are handled more efficiently. As of now the dirmngr
needs service from gpgsm thus it is best to call it through gpgsm:
gpgsm --call-dirmngr LOAD /absolute/filename/to/a/CRL/file
See the dirmngr README and manual for further details.
If you don't want to check CRLs, use the option --diable-crl-checks
with gpgsm.
> I'm trying to replace the S/MIME support in OpenSSL with gpgsm for the
> MUA Gnus.
Great; I'd love it.
> Perhaps I shouldn't be using gpgsm directly? gpgme didn't seem to
> have a command line front end.
For Gnus it makes sense to use gpgsm directly. Enhancing pgg to
support gpgsm should not be that hard. Things you need to take care
off are: Warn if GPG_AGENT_INFO has not been set, because this will
call gpg-agent for each operation and obviously does not cache the
passphrase them. If GPG_AGENT_INFO has been set, also disable the
passphrase code for gpg and pass --use-agent to gpg - this way gpg
benefits from the passphrase caching and the pinentry.
You may want to look at gpgconf (tools/README.gpgconf) to provide a
customization interface for gpgsm, gpg-agent and dirmngr.
Module Overview
================
gpgsm
libgpg-error
libgcrypt
libksba
libassuan [statically linked]
[Standard system libraries]
gpg-agent
libgpg-error
libgcrypt
libassuan [statically linked]
libpth [system library]
[Standard system libraries]
scdaemon
libgpg-error
libgcrypt
libksba
libassuan [statically linked]
libusb [system library, optional]
libopensc [system library, optional]
[For reader access libpcsclite or a CT-API library may be
linked at runtime (controllable by scdaemon.conf)]
[Standard system libraries]
gpg-protect-tool
libgpg-error
libgcrypt
[Standard system libraries]
dirmngr
libgpg-error
libgcrypt
libksba
libassuan [statically linked]
libldap [system library]
liblber [system library]
libsasl [system library, required by libldap]
libdb2 [system library, required by libsasl]
libcrypt [system library, required by libsasl - OOPS]
libpam [system library, required by libsasl]
[Standard system libraries]
pinentry-curses
libncurses
[Standard system libraries]
[Independent Assuan code is source included]
pinentry-gtk
libncurses
[GTK+ and X libraries]
[Standard system libraries]
[Independent Assuan code is source included]
pinentry-qt
libncurses
[QT and X libraries]
[Standard system libraries]
[Independent Assuan code is source included]
gpgme
[Standard system libraries]
[gpgsm is required at runtime]
[Independent Assuan code is source included]
libgpg-error
[none]
libgcrypt
libgpg-error
libksba
libgpg-error
libassuan
[none]
diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi
index a1e42525d..5e86cf36a 100644
--- a/doc/dirmngr.texi
+++ b/doc/dirmngr.texi
@@ -1,1186 +1,1186 @@
@c Copyright (C) 2002 Klar"alvdalens Datakonsult AB
@c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking DIRMNGR
@chapter Invoking DIRMNGR
@cindex DIRMNGR command options
@cindex command options
@cindex options, DIRMNGR command
@manpage dirmngr.8
@ifset manverb
.B dirmngr
\- CRL and OCSP daemon
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@mansect description
Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing
the OpenPGP keyservers. As with previous versions it is also used as
a server for managing and downloading certificate revocation lists
(CRLs) for X.509 certificates, downloading X.509 certificates, and
providing access to OCSP providers. Dirmngr is invoked internally by
@command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent}
tool.
@manpause
@noindent
@xref{Option Index},for an index to @command{DIRMNGR}'s commands and
options.
@mancont
@menu
* Dirmngr Commands:: List of all commands.
* Dirmngr Options:: List of all options.
* Dirmngr Configuration:: Configuration files.
* Dirmngr Signals:: Use of signals.
* Dirmngr Examples:: Some usage examples.
* Dirmngr Protocol:: The protocol dirmngr uses.
@end menu
@node Dirmngr Commands
@section Commands
@mansect commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
This is only used for testing.
@item --daemon
@opindex daemon
Run in background daemon mode and listen for commands on a socket.
This is the way @command{dirmngr} is started on demand by the other
GnuPG components. To force starting @command{dirmngr} it is in
general best to use @code{gpgconf --launch dirmngr}.
@item --supervised
@opindex supervised
Run in the foreground, sending logs to stderr, and listening on file
descriptor 3, which must already be bound to a listening socket. This
is useful when running under systemd or other similar process
supervision schemes. This option is not supported on Windows.
@item --list-crls
@opindex list-crls
List the contents of the CRL cache on @code{stdout}. This is probably
only useful for debugging purposes.
@item --load-crl @var{file}
@opindex load-crl
This command requires a filename as additional argument, and it will
make Dirmngr try to import the CRL in @var{file} into it's cache.
Note, that this is only possible if Dirmngr is able to retrieve the
CA's certificate directly by its own means. In general it is better
to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command
so that @code{gpgsm} can help dirmngr.
@item --fetch-crl @var{url}
@opindex fetch-crl
This command requires an URL as additional argument, and it will make
dirmngr try to retrieve and import the CRL from that @var{url} into
it's cache. This is mainly useful for debugging purposes. The
@command{dirmngr-client} provides the same feature for a running dirmngr.
@item --shutdown
@opindex shutdown
This commands shuts down an running instance of Dirmngr. This command
has currently no effect.
@item --flush
@opindex flush
This command removes all CRLs from Dirmngr's cache. Client requests
will thus trigger reading of fresh CRLs.
@end table
@mansect options
@node Dirmngr Options
@section Option Summary
Note that all long options with the exception of @option{--options}
and @option{--homedir} may also be given in the configuration file
after stripping off the two leading dashes.
@table @gnupgtabopt
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{dirmngr.conf} and expected in the home directory.
@item --homedir @var{dir}
@opindex options
Set the name of the home directory to @var{dir}. This option is only
effective when used on the command line. The default is
the directory named @file{.gnupg} directly below the home directory
of the user unless the environment variable @code{GNUPGHOME} has been set
in which case its value will be used. Many kinds of data are stored within
this directory.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @option{-vv}.
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does. Use @file{socket://} to log to
socket.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be a
numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debug flags. All flags are or-ed and @var{flags} may be given in
C syntax (e.g. 0x0042) or as a comma separated list of flag names. To
get a list of all supported flags the single word "help" can be used.
This option is only useful for debugging and the behavior may change
at any time without notice.
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --tls-debug @var{level}
@opindex tls-debug
Enable debugging of the TLS layer at @var{level}. The details of the
debug level depend on the used TLS library and are not set in stone.
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item --disable-check-own-socket
@opindex disable-check-own-socket
On some platforms @command{dirmngr} is able to detect the removal of
its socket file and shutdown itself. This option disable this
self-test for debugging purposes.
@item -s
@itemx --sh
@itemx -c
@itemx --csh
@opindex s
@opindex sh
@opindex c
@opindex csh
Format the info output in daemon mode for use with the standard Bourne
shell respective the C-shell. The default is to guess it based on the
environment variable @code{SHELL} which is in almost all cases
sufficient.
@item --force
@opindex force
Enabling this option forces loading of expired CRLs; this is only
useful for debugging.
@item --use-tor
@itemx --no-use-tor
@opindex use-tor
@opindex no-use-tor
The option @option{--use-tor} switches Dirmngr and thus GnuPG into
``Tor mode'' to route all network access via Tor (an anonymity
network). Certain other features are disabled in this mode. The
effect of @option{--use-tor} cannot be overridden by any other command
or even by reloading dirmngr. The use of @option{--no-use-tor}
disables the use of Tor. The default is to use Tor if it is available
on startup or after reloading dirmngr.
@item --standard-resolver
@opindex standard-resolver
This option forces the use of the system's standard DNS resolver code.
This is mainly used for debugging. Note that on Windows a standard
resolver is not used and all DNS access will return the error ``Not
Implemented'' if this option is used. Using this together with enabled
Tor mode returns the error ``Not Enabled''.
@item --recursive-resolver
@opindex recursive-resolver
When possible use a recursive resolver instead of a stub resolver.
@item --resolver-timeout @var{n}
@opindex resolver-timeout
Set the timeout for the DNS resolver to N seconds. The default are 30
seconds.
@item --connect-timeout @var{n}
@item --connect-quick-timeout @var{n}
@opindex connect-timeout
@opindex connect-quick-timeout
Set the timeout for HTTP and generic TCP connection attempts to N
seconds. The value set with the quick variant is used when the
--quick option has been given to certain Assuan commands. The quick
value is capped at the value of the regular connect timeout. The
default values are 15 and 2 seconds. Note that the timeout values are
for each connection attempt; the connection code will attempt to
connect all addresses listed for a server.
@item --listen-backlog @var{n}
@opindex listen-backlog
Set the size of the queue for pending connections. The default is 64.
@item --allow-version-check
@opindex allow-version-check
Allow Dirmngr to connect to @code{https://versions.gnupg.org} to get
the list of current software versions. If this option is enabled
the list is retrieved in case the local
copy does not exist or is older than 5 to 7 days. See the option
@option{--query-swdb} of the command @command{gpgconf} for more
details. Note, that regardless of this option a version check can
always be triggered using this command:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --keyserver @var{name}
@opindex keyserver
Use @var{name} as your keyserver. This is the server that @command{gpg}
communicates with to receive keys, send keys, and search for
keys. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP
keyservers, or "mailto" for the Graff email keyserver. Note that your
particular installation of GnuPG may have other keyserver types
available as well. Keyserver schemes are case-insensitive. After the
keyserver name, optional keyserver configuration options may be
provided. These are the same as the @option{--keyserver-options} of
@command{gpg}, but apply only to this particular keyserver.
Most keyservers synchronize with each other, so there is generally no
need to send keys to more than one server. The keyserver
@code{hkp://keys.gnupg.net} uses round robin DNS to give a different
keyserver each time you use it.
If exactly two keyservers are configured and only one is a Tor hidden
service (.onion), Dirmngr selects the keyserver to use depending on
whether Tor is locally running or not. The check for a running Tor is
done for each new connection.
If no keyserver is explicitly configured, dirmngr will use the
built-in default of hkps://hkps.pool.sks-keyservers.net.
@item --nameserver @var{ipaddr}
@opindex nameserver
In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS
names. If the default public resolver, which is @code{8.8.8.8}, shall
not be used a different one can be given using this option. Note that
a numerical IP address must be given (IPv6 or IPv4) and that no error
checking is done for @var{ipaddr}.
@item --disable-ipv4
@item --disable-ipv6
@opindex disable-ipv4
@opindex disable-ipv6
Disable the use of all IPv4 or IPv6 addresses.
@item --disable-ldap
@opindex disable-ldap
Entirely disables the use of LDAP.
@item --disable-http
@opindex disable-http
Entirely disables the use of HTTP.
@item --ignore-http-dp
@opindex ignore-http-dp
When looking for the location of a CRL, the to be tested certificate
usually contains so called @dfn{CRL Distribution Point} (DP) entries
which are URLs describing the way to access the CRL. The first found DP
entry is used. With this option all entries using the @acronym{HTTP}
scheme are ignored when looking for a suitable DP.
@item --ignore-ldap-dp
@opindex ignore-ldap-dp
This is similar to @option{--ignore-http-dp} but ignores entries using
the @acronym{LDAP} scheme. Both options may be combined resulting in
ignoring DPs entirely.
@item --ignore-ocsp-service-url
@opindex ignore-ocsp-service-url
Ignore all OCSP URLs contained in the certificate. The effect is to
force the use of the default responder.
@item --honor-http-proxy
@opindex honor-http-proxy
If the environment variable @env{http_proxy} has been set, use its
value to access HTTP servers.
@item --http-proxy @var{host}[:@var{port}]
@opindex http-proxy
@efindex http_proxy
Use @var{host} and @var{port} to access HTTP servers. The use of this
option overrides the environment variable @env{http_proxy} regardless
whether @option{--honor-http-proxy} has been set.
@item --ldap-proxy @var{host}[:@var{port}]
@opindex ldap-proxy
Use @var{host} and @var{port} to connect to LDAP servers. If @var{port}
is omitted, port 389 (standard LDAP port) is used. This overrides any
specified host and port part in a LDAP URL and will also be used if host
and port have been omitted from the URL.
@item --only-ldap-proxy
@opindex only-ldap-proxy
Never use anything else but the LDAP "proxy" as configured with
@option{--ldap-proxy}. Usually @command{dirmngr} tries to use other
configured LDAP server if the connection using the "proxy" failed.
@item --ldapserverlist-file @var{file}
@opindex ldapserverlist-file
Read a list of LDAP servers to consult for CRLs and certificates from
file. This servers from this list are used after any servers set by a
client for its session. The default value for @var{file} is
@file{dirmngr_ldapservers.conf}.
This server list file contains one LDAP server per line in the format
@sc{hostname:port:username:password:base_dn:flags}
Lines starting with a @samp{#} are comments.
The only defined flag is @code{ldaps} to specify that a TLS
connections shall be used. Flags are comma delimited; unknown flags
are ignored.
Note that as usual all strings entered are expected to be UTF-8 encoded.
Obviously this will lead to problems if the password has originally been
encoded as Latin-1. There is no other solution here than to put such a
password in the binary encoding into the file (i.e. non-ascii characters
won't show up readable).@footnote{The @command{gpgconf} tool might be
helpful for frontends as it enables editing this configuration file using
percent-escaped strings.}
@item --ldaptimeout @var{secs}
@opindex ldaptimeout
Specify the number of seconds to wait for an LDAP query before timing
out. The default are 15 seconds. 0 will never timeout.
@item --add-servers
@opindex add-servers
This option makes dirmngr add any servers it discovers when validating
certificates against CRLs to the internal list of servers to consult for
certificates and CRLs.
This option is useful when trying to validate a certificate that has
a CRL distribution point that points to a server that is not already
listed in the ldapserverlist. Dirmngr will always go to this server and
try to download the CRL, but chances are high that the certificate used
to sign the CRL is located on the same server. So if dirmngr doesn't add
that new server to list, it will often not be able to verify the
signature of the CRL unless the @code{--add-servers} option is used.
Note: The current version of dirmngr has this option disabled by default.
@item --allow-ocsp
@opindex allow-ocsp
This option enables OCSP support if requested by the client.
OCSP requests are rejected by default because they may violate the
privacy of the user; for example it is possible to track the time when
a user is reading a mail.
@item --ocsp-responder @var{url}
@opindex ocsp-responder
Use @var{url} as the default OCSP Responder if the certificate does
not contain information about an assigned responder. Note, that
@code{--ocsp-signer} must also be set to a valid certificate.
@item --ocsp-signer @var{fpr}|@var{file}
@opindex ocsp-signer
Use the certificate with the fingerprint @var{fpr} to check the
responses of the default OCSP Responder. Alternatively a filename can be
given in which case the response is expected to be signed by one of the
certificates described in that file. Any argument which contains a
slash, dot or tilde is considered a filename. Usual filename expansion
takes place: A tilde at the start followed by a slash is replaced by the
content of @env{HOME}, no slash at start describes a relative filename
which will be searched at the home directory. To make sure that the
@var{file} is searched in the home directory, either prepend the name
with "./" or use a name which contains a dot.
If a response has been signed by a certificate described by these
fingerprints no further check upon the validity of this certificate is
done.
The format of the @var{FILE} is a list of SHA-1 fingerprint, one per
line with optional colons between the bytes. Empty lines and lines
prefix with a hash mark are ignored.
@item --ocsp-max-clock-skew @var{n}
@opindex ocsp-max-clock-skew
The number of seconds a skew between the OCSP responder and them local
clock is accepted. Default is 600 (10 minutes).
@item --ocsp-max-period @var{n}
@opindex ocsp-max-period
Seconds a response is at maximum considered valid after the time given
in the thisUpdate field. Default is 7776000 (90 days).
@item --ocsp-current-period @var{n}
@opindex ocsp-current-period
The number of seconds an OCSP response is considered valid after the
time given in the NEXT_UPDATE datum. Default is 10800 (3 hours).
@item --max-replies @var{n}
@opindex max-replies
Do not return more that @var{n} items in one query. The default is
10.
@item --ignore-cert-extension @var{oid}
@opindex ignore-cert-extension
Add @var{oid} to the list of ignored certificate extensions. The
@var{oid} is expected to be in dotted decimal form, like
@code{2.5.29.3}. This option may be used more than once. Critical
flagged certificate extensions matching one of the OIDs in the list
are treated as if they are actually handled and thus the certificate
won't be rejected due to an unknown critical extension. Use this
option with care because extensions are usually flagged as critical
for a reason.
@item --hkp-cacert @var{file}
Use the root certificates in @var{file} for verification of the TLS
certificates used with @code{hkps} (keyserver access over TLS). If
the file is in PEM format a suffix of @code{.pem} is expected for
@var{file}. This option may be given multiple times to add more
root certificates. Tilde expansion is supported.
If no @code{hkp-cacert} directive is present, dirmngr will make a
reasonable choice: if the keyserver in question is the special pool
@code{hkps.pool.sks-keyservers.net}, it will use the bundled root
certificate for that pool. Otherwise, it will use the system CAs.
@end table
@c
@c Dirmngr Configuration
@c
@mansect files
@node Dirmngr Configuration
@section Configuration
Dirmngr makes use of several directories when running in daemon mode:
There are a few configuration files whih control the operation of
dirmngr. By default they may all be found in the current home
directory (@pxref{option --homedir}).
@table @file
@item dirmngr.conf
@efindex dirmngr.conf
This is the standard configuration file read by @command{dirmngr} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This file
is also read after a @code{SIGHUP} however not all options will
actually have an effect. This default name may be changed on the
command line (@pxref{option --options}). You should backup this file.
@item /etc/gnupg/trusted-certs
This directory should be filled with certificates of Root CAs you
are trusting in checking the CRLs and signing OCSP Responses.
Usually these are the same certificates you use with the applications
making use of dirmngr. It is expected that each of these certificate
files contain exactly one @acronym{DER} encoded certificate in a file
with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads
those certificates on startup and when given a SIGHUP. Certificates
which are not readable or do not make up a proper X.509 certificate
are ignored; see the log file for details.
Applications using dirmngr (e.g. gpgsm) can request these
certificates to complete a trust chain in the same way as with the
extra-certs directory (see below).
Note that for OCSP responses the certificate specified using the option
@option{--ocsp-signer} is always considered valid to sign OCSP requests.
@item /etc/gnupg/extra-certs
This directory may contain extra certificates which are preloaded
into the internal cache on startup. Applications using dirmngr (e.g. gpgsm)
can request cached certificates to complete a trust chain.
This is convenient in cases you have a couple intermediate CA certificates
or certificates usually used to sign OCSP responses.
These certificates are first tried before going
out to the net to look for them. These certificates must also be
@acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}.
@item ~/.gnupg/crls.d
This directory is used to store cached CRLs. The @file{crls.d}
part will be created by dirmngr if it does not exists but you need to
make sure that the upper directory exists.
@end table
@manpause
To be able to see what's going on you should create the configure file
@file{~/gnupg/dirmngr.conf} with at least one line:
@example
log-file ~/dirmngr.log
@end example
To be able to perform OCSP requests you probably want to add the line:
@example
allow-ocsp
@end example
To make sure that new options are read and that after the installation
of a new GnuPG versions the installed dirmngr is running, you may want
to kill an existing dirmngr first:
@example
gpgconf --kill dirmngr
@end example
You may check the log file to see whether all desired root
certificates have been loaded correctly.
@c
@c Dirmngr Signals
@c
@mansect signals
@node Dirmngr Signals
@section Use of signals
A running @command{dirmngr} may be controlled by signals, i.e. using
the @command{kill} command to send a signal to the process.
Here is a list of supported signals:
@table @gnupgtabopt
@item SIGHUP
@cpindex SIGHUP
This signal flushes all internally cached CRLs as well as any cached
certificates. Then the certificate cache is reinitialized as on
startup. Options are re-read from the configuration file. Instead of
sending this signal it is better to use
@example
gpgconf --reload dirmngr
@end example
@item SIGTERM
@cpindex SIGTERM
Shuts down the process but waits until all current requests are
fulfilled. If the process has received 3 of these signals and requests
are still pending, a shutdown is forced. You may also use
@example
gpgconf --kill dirmngr
@end example
instead of this signal
@item SIGINT
@cpindex SIGINT
Shuts down the process immediately.
@item SIGUSR1
@cpindex SIGUSR1
This prints some caching statistics to the log file.
@end table
@c
@c Examples
@c
@mansect examples
@node Dirmngr Examples
@section Examples
Here is an example on how to show dirmngr's internal table of OpenPGP
keyserver addresses. The output is intended for debugging purposes
and not part of a defined API.
@example
gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye
@end example
To inhibit the use of a particular host you have noticed in one of the
keyserver pools, you may use
@example
gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye
@end example
The description of the @code{keyserver} command can be printed using
@example
gpg-connect-agent --dirmngr 'help keyserver' /bye
@end example
@c
@c Assuan Protocol
@c
@manpause
@node Dirmngr Protocol
@section Dirmngr's Assuan Protocol
Assuan is the IPC protocol used to access dirmngr. This is a
description of the commands implemented by dirmngr.
@menu
* Dirmngr LOOKUP:: Look up a certificate via LDAP
* Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP.
* Dirmngr CHECKCRL:: Validate a certificate using a CRL.
* Dirmngr CHECKOCSP:: Validate a certificate using OCSP.
* Dirmngr CACHECERT:: Put a certificate into the internal cache.
* Dirmngr VALIDATE:: Validate a certificate for debugging.
@end menu
@node Dirmngr LOOKUP
@subsection Return the certificate(s) found
Lookup certificate. To allow multiple patterns (which are ORed)
quoting is required: Spaces are to be translated into "+" or into
"%20"; obviously this requires that the usual escape quoting rules
are applied. The server responds with:
@example
S: D <DER encoded certificate>
S: END
S: D <second DER encoded certificate>
S: END
S: OK
@end example
In this example 2 certificates are returned. The server may return
any number of certificates; OK will also be returned when no
certificates were found. The dirmngr might return a status line
@example
S: S TRUNCATED <n>
@end example
To indicate that the output was truncated to N items due to a
limitation of the server or by an arbitrary set limit.
The option @option{--url} may be used if instead of a search pattern a
complete URL to the certificate is known:
@example
C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate
@end example
If the option @option{--cache-only} is given, no external lookup is done
so that only certificates from the cache are returned.
With the option @option{--single}, the first and only the first match
will be returned. Unless option @option{--cache-only} is also used, no
local lookup will be done in this case.
@node Dirmngr ISVALID
@subsection Validate a certificate using a CRL or OCSP
@example
ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr}
@end example
Check whether the certificate described by the @var{certid} has been
revoked. Due to caching, the Dirmngr is able to answer immediately in
most cases.
The @var{certid} is a hex encoded string consisting of two parts,
delimited by a single dot. The first part is the SHA-1 hash of the
issuer name and the second part the serial number.
Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be
given in which case an OCSP request is done before consulting the CRL.
If the option @option{--only-ocsp} is given, no fallback to a CRL check
will be used. If the option @option{--force-default-responder} is
given, only the default OCSP responder will be used and any other
methods of obtaining an OCSP responder URL won't be used.
@noindent
Common return values are:
@table @code
@item GPG_ERR_NO_ERROR (0)
This is the positive answer: The certificate is not revoked and we have
an up-to-date revocation list for that certificate. If OCSP was used
the responder confirmed that the certificate has not been revoked.
@item GPG_ERR_CERT_REVOKED
This is the negative answer: The certificate has been revoked. Either
it is in a CRL and that list is up to date or an OCSP responder informed
us that it has been revoked.
@item GPG_ERR_NO_CRL_KNOWN
No CRL is known for this certificate or the CRL is not valid or out of
date.
@item GPG_ERR_NO_DATA
The OCSP responder returned an ``unknown'' status. This means that it
is not aware of the certificate's status.
@item GPG_ERR_NOT_SUPPORTED
This is commonly seen if OCSP support has not been enabled in the
configuration.
@end table
If DirMngr has not enough information about the given certificate (which
is the case for not yet cached certificates), it will inquire the
missing data:
@example
S: INQUIRE SENDCERT <CertID>
C: D <DER encoded certificate>
C: END
@end example
A client should be aware that DirMngr may ask for more than one
certificate.
If Dirmngr has a certificate but the signature of the certificate
could not been validated because the root certificate is not known to
dirmngr as trusted, it may ask back to see whether the client trusts
this the root certificate:
@example
S: INQUIRE ISTRUSTED <CertHexfpr>
C: D 1
C: END
@end example
Only this answer will let Dirmngr consider the certificate as valid.
@node Dirmngr CHECKCRL
@subsection Validate a certificate using a CRL
Check whether the certificate with FINGERPRINT (SHA-1 hash of the
entire X.509 certificate blob) is valid or not by consulting the CRL
responsible for this certificate. If the fingerprint has not been
given or the certificate is not known, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match FINGERPRINT) as a binary blob. Processing then
takes place without further interaction; in particular dirmngr tries
to locate other required certificate by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CHECKOCSP
@subsection Validate a certificate using OCSP
@example
CHECKOCSP [--force-default-responder] [@var{fingerprint}]
@end example
Check whether the certificate with @var{fingerprint} (the SHA-1 hash of
the entire X.509 certificate blob) is valid by consulting the appropriate
OCSP responder. If the fingerprint has not been given or the
certificate is not known by Dirmngr, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match @var{fingerprint}) as a binary blob. Processing
then takes place without further interaction; in particular dirmngr
tries to locate other required certificates by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
If the option @option{--force-default-responder} is given, only the
default OCSP responder is used. This option is the per-command variant
of the global option @option{--ignore-ocsp-service-url}.
@noindent
The return code is 0 for success; i.e. the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CACHECERT
@subsection Put a certificate into the internal cache
Put a certificate into the internal cache. This command might be
useful if a client knows in advance certificates required for a test and
wants to make sure they get added to the internal cache. It is also
helpful for debugging. To get the actual certificate, this command
immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@noindent
The return code is 0 for success; i.e. the certificate has not been
successfully cached or one of the usual error codes from libgpg-error.
@node Dirmngr VALIDATE
@subsection Validate a certificate for debugging
Validate a certificate using the certificate validation function used
internally by dirmngr. This command is only useful for debugging. To
get the actual certificate, this command immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@mansect see also
@ifset isman
@command{gpgsm}(1),
@command{dirmngr-client}(1)
@end ifset
@include see-also-note.texi
@c
@c !!! UNDER CONSTRUCTION !!!
@c
@c
@c @section Verifying a Certificate
@c
@c There are several ways to request services from Dirmngr. Almost all of
@c them are done using the Assuan protocol. What we describe here is the
@c Assuan command CHECKCRL as used for example by the dirmnr-client tool if
@c invoked as
@c
@c @example
@c dirmngr-client foo.crt
@c @end example
@c
@c This command will send an Assuan request to an already running Dirmngr
@c instance. foo.crt is expected to be a standard X.509 certificate and
@c dirmngr will receive the Assuan command
@c
@c @example
@c CHECKCRL @var [{fingerprint}]
@c @end example
@c
@c @var{fingerprint} is optional and expected to be the SHA-1 has of the
@c DER encoding of the certificate under question. It is to be HEX
@c encoded. The rationale for sending the fingerprint is that it allows
@c dirmngr to reply immediately if it has already cached such a request. If
@c this is not the case and no certificate has been found in dirmngr's
@c internal certificate storage, dirmngr will request the certificate using
@c the Assuan inquiry
@c
@c @example
@c INQUIRE TARGETCERT
@c @end example
@c
@c The caller (in our example dirmngr-client) is then expected to return
@c the certificate for the request (which should match @var{fingerprint})
@c as a binary blob.
@c
@c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This
@c function checks whether a CRL item exists for target certificate. These
@c CRL items are kept in a database of already loaded and verified CRLs.
@c This mechanism is called the CRL cache. Obviously timestamps are kept
@c there with each item to cope with the expiration date of the CRL. The
@c possible return values are: @code{0} to indicate that a valid CRL is
@c available for the certificate and the certificate itself is not listed
@c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is
@c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no
@c information is available. The first two codes are immediately returned to
@c the caller and the processing of this request has been done.
@c
@c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now
@c calls @code{clr_cache_reload_crl} and if this succeeds calls
@c @code{crl_cache_cert_isvald) once more. All further errors are
@c immediately returned to the caller.
@c
@c @code{crl_cache_reload_crl} is the actual heart of the CRL management.
@c It locates the corresponding CRL for the target certificate, reads and
@c verifies this CRL and stores it in the CRL cache. It works like this:
@c
@c * Loop over all crlDPs in the target certificate.
@c * If the crlDP is invalid immediately terminate the loop.
@c * Loop over all names in the current crlDP.
@c * If the URL scheme is unknown or not enabled
@c (--ignore-http-dp, --ignore-ldap-dp) continues with
@c the next name.
@c * @code{crl_fetch} is called to actually retrieve the CRL.
@c In case of problems this name is ignore and we continue with
@c the next name. Note that @code{crl_fetch} does only return
@c a descriptor for the CRL for further reading so does the CRL
@c does not yet end up in memory.
@c * @code{crl_cache_insert} is called with that descriptor to
@c actually read the CRL into the cache. See below for a
@c description of this function. If there is any error (e.g. read
@c problem, CRL not correctly signed or verification of signature
@c not possible), this descriptor is rejected and we continue
@c with the next name. If the CRL has been successfully loaded,
@c the loop is terminated.
@c * If no crlDP has been found in the previous loop use a default CRL.
@c Note, that if any crlDP has been found but loading of the CRL failed,
@c this condition is not true.
@c * Try to load a CRL from all configured servers (ldapservers.conf)
@c in turn. The first server returning a CRL is used.
@c * @code(crl_cache_insert) is then used to actually insert the CRL
@c into the cache. If this failed we give up immediately without
@c checking the rest of the servers from the first step.
@c * Ready.
@c
@c
@c The @code{crl_cache_insert} function takes care of reading the bulk of
@c the CRL, parsing it and checking the signature. It works like this: A
@c new database file is created using a temporary file name. The CRL
@c parsing machinery is started and all items of the CRL are put into
@c this database file. At the end the issuer certificate of the CRL
@c needs to be retrieved. Three cases are to be distinguished:
@c
@c a) An authorityKeyIdentifier with an issuer and serialno exits: The
@c certificate is retrieved using @code{find_cert_bysn}. If
@c the certificate is in the certificate cache, it is directly
@c returned. Then the requester (i.e. the client who requested the
@c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested issuer and seriano (This is
@c needed because the LDAP layer may return several certificates as
@c LDAP as no standard way to retrieve by serial number).
@c
@c b) An authorityKeyIdentifier with a key ID exists: The certificate is
@c retrieved using @code{find_cert_bysubject}. If the certificate is
@c in the certificate cache, it is directly returned. Then the
@c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject and key ID.
@c
@c c) No authorityKeyIdentifier exits: The certificate is retrieved
@c using @code{find_cert_bysubject} without the key ID argument. If
@c the certificate is in the certificate cache the first one with a
@c matching subject is directly returned. Then the requester is
@c asked via the Assuan inquiry ``SENDCERT'' and an exact
@c specification of the subject whether he can
@c provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject; the first certificate
@c with a matching subject is then returned.
@c
@c If no certificate was found, the function returns with the error
@c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails,
-@c the erro is returned. On success the @code{validate_cert_chain} is
+@c the error is returned. On success the @code{validate_cert_chain} is
@c used to verify that the certificate is actually valid.
@c
@c Here we may encounter a recursive situation:
@c @code{validate_cert_chain} needs to look at other certificates and
@c also at CRLs to check whether these other certificates and well, the
@c CRL issuer certificate itself are not revoked. FIXME: We need to make
@c sure that @code{validate_cert_chain} does not try to lookup the CRL we
@c are currently processing. This would be a catch-22 and may indicate a
@c broken PKI. However, due to overlapping expiring times and imprecise
@c clocks this may actually happen.
@c
@c For historical reasons the Assuan command ISVALID is a bit different
@c to CHECKCRL but this is mainly due to different calling conventions.
@c In the end the same fucntionality is used, albeit hidden by a couple
@c of indirection and argument and result code mangling. It furthere
@c ingetrages OCSP checking depending on options are the way it is
@c called. GPGSM still uses this command but might eventually switch over
@c to CHECKCRL and CHECKOCSP so that ISVALID can be retired.
@c
@c
@c @section Validating a certificate
@c
@c We describe here how the internal function @code{validate_cert_chain}
@c works. Note that mainly testing purposes this functionality may be
@c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}.
@c
@c The function takes the target certificate and a mode argument as
@c parameters and returns an error code and optionally the closes
@c expiration time of all certificates in the chain.
@c
@c We first check that the certificate may be used for the requested
@c purpose (i.e. OCSP or CRL signing). If this is not the case
@c GPG_ERR_WRONG_KEY_USAGE is returned.
@c
@c The next step is to find the trust anchor (root certificate) and to
@c assemble the chain in memory: Starting with the target certificate,
@c the expiration time is checked against the current date, unknown
@c critical extensions are detected and certificate policies are matched
@c (We only allow 2.289.9.9 but I have no clue about that OID and from
@c where I got it - it does not even seem to be assigned - debug cruft?).
@c
@c Now if this certificate is a self-signed one, we have reached the
@c trust anchor. In this case we check that the signature is good, the
@c certificate is allowed to act as a CA, that it is a trusted one (by
@c checking whether it is has been put into the trusted-certs
@c configuration directory) and finally prepend into to our list
@c representing the certificate chain. This steps ends then.
@c
@c If it is not a self-signed certificate, we check that the chain won't
@c get too long (current limit is 100), if this is the case we terminate
@c with the error GPG_ERR_BAD_CERT_CHAIN.
@c
@c Now the issuer's certificate is looked up: If an
@c authorityKeyIdentifier is available, this one is used to locate the
@c certificate either using issuer and serialnumber or subject DN
@c (i.e. the issuer's DN) and the keyID. The functions
@c @code{find_cert_bysn) and @code{find_cert_bysubject} are used
@c respectively. The have already been described above under the
@c description of @code{crl_cache_insert}. If no certificate was found
@c or with no authorityKeyIdentifier, only the cache is consulted using
@c @code{get_cert_bysubject}. The latter is done under the assumption
@c that a matching certificate has explicitly been put into the
@c certificate cache. If the issuer's certificate could not be found,
@c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}.
@c
@c If the issuer's certificate has been found, the signature of the
@c actual certificate is checked and in case this fails the error
@c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the
@c maximum chain length of the issuing certificate is checked as well as
@c the capability of the certificate (i.e. whether he may be used for
@c certificate signing). Then the certificate is prepended to our list
@c representing the certificate chain. Finally the loop is continued now
@c with the issuer's certificate as the current certificate.
@c
@c After the end of the loop and if no error as been encountered
@c (i.e. the certificate chain has been assempled correctly), a check is
@c done whether any certificate expired or a critical policy has not been
@c met. In any of these cases the validation terminates with an
@c appropriate error.
@c
@c Finally the function @code{check_revocations} is called to verify no
@c certificate in the assempled chain has been revoked: This is an
@c recursive process because a CRL has to be checked for each certificate
@c in the chain except for the root certificate, of which we already know
@c that it is trusted and we avoid checking a CRL here due to common
@c setup problems and the assumption that a revoked root certificate has
@c been removed from the list of trusted certificates.
@c
@c
@c
@c
@c @section Looking up certificates through LDAP.
@c
@c This describes the LDAP layer to retrieve certificates.
@c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are
@c used for this. The first one starts a search and the second one is
@c used to retrieve certificate after certificate.
@c
diff --git a/doc/examples/pwpattern.list b/doc/examples/pwpattern.list
index 251c2d40b..fb84754c0 100644
--- a/doc/examples/pwpattern.list
+++ b/doc/examples/pwpattern.list
@@ -1,48 +1,48 @@
# pwpattern.list -*- default-generic -*-
#
# This is an example for a pattern file as used by gpg-check-pattern.
# The file is line based with comment lines beginning on the *first*
# position with a '#'. Empty lines and lines with just spaces are
# ignored. The other lines may be verbatim patterns and match as they
# are (trailing spaces are ignored) or extended regular expressions
# indicated by a / in the first column and terminated by another / or
# end of line. All comparisons are case insensitive.
# Reject the usual metavariables. Usual not required because
# gpg-agent can be used to reject all passphrases shorter than 8
-# charactes.
+# characters.
foo
bar
baz
# As well as very common passwords. Note that gpg-agent can be used
# to reject them due to missing non-alpha characters.
password
passwort
passphrase
mantra
test
abc
egal
# German number plates.
/^[A-Z]{1,3}[ ]*-[ ]*[A-Z]{1,2}[ ]*[0-9]+/
# Dates (very limited, only ISO dates). */
/^[012][0-9][0-9][0-9]-[012][0-9]-[0123][0-9]$/
# Arbitrary strings
the quick brown fox jumps over the lazy dogs back
no-password
no password
12345678
123456789
1234567890
87654321
987654321
0987654321
qwertyuiop
qwertzuiop
asdfghjkl
zxcvbnm
diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi
index fcc1792f1..0865798d2 100644
--- a/doc/gpg-card.texi
+++ b/doc/gpg-card.texi
@@ -1,652 +1,652 @@
@c card-tool.texi - man page for gpg-card-tool
@c Copyright (C) 2019 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Smart Card Tool
@chapter Smart Card Tool
GnuPG comes with a tool to administrate smart cards and USB tokens.
This tool is an enhanced version of the @option{--edit-key} command
available with @command{gpg}.
@menu
* gpg-card:: Administrate smart cards.
@end menu
@c
@c GPG-CARD-TOOL
@c
@manpage gpg-card.1
@node gpg-card
@section Administrate smart cards.
@ifset manverb
.B gpg-card
\- Administrate Smart Cards
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-card
.RI [ options ]
.br
.B gpg-card
.RI [ options ]
.I command
.RI {
.B --
.I command
.RI }
@end ifset
@mansect description
The @command{gpg-card} is used to administrate smart cards and USB
tokens. It provides a superset of features from @command{gpg
--card-edit} an can be considered a frontend to @command{scdaemon}
which is a daemon started by @command{gpg-agent} to handle smart
cards.
If @command{gpg-card} is invoked without commands an interactive
mode is used.
If @command{gpg-card} is invoked with one or more commands the
same commands as available in the interactive mode are run from the
command line. These commands need to be delimited with a double-dash.
If a double-dash or a shell specific character is required as part of
a command the entire command needs to be put in quotes. If one of
-those commands returns an error the remaining commands are mot anymore
+those commands returns an error the remaining commands are not anymore
run unless the command was prefixed with a single dash.
A list of commands is available by using the command @code{help} and a
detailed description of each command is printed by using @code{help
COMMAND}.
See the NOTES sections for instructions pertaining to specific cards
or card applications.
@mansect options
@noindent
@command{gpg-card} understands these options:
@table @gnupgtabopt
@item --with-colons
@opindex with-colons
This option has currently no effect.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns only the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --quiet
@opindex quiet
Disable almost all informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent if it has not yet been started and its
service is required. This option is mostly useful on machines where
the connection to gpg-agent has been redirected to another machines.
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}.
@item --gpg-program @var{file}
@opindex gpg-program
Specify a non-default gpg binary to be used by certain commands.
@item --gpgsm-program @var{file}
@opindex gpgsm-program
Specify a non-default gpgsm binary to be used by certain commands.
@end table
@mansect notes (OpenPGP)
The support for OpenPGP cards in @command{gpg-card} is not yet
complete. For missing features, please continue to use @code{gpg
--card-edit}.
@mansect notes (PIV)
@noindent
GnuPG has support for PIV cards (``Personal Identity Verification''
as specified by NIST Special Publication 800-73-4). This section
describes how to initialize (personalize) a fresh Yubikey token
featuring the PIV application (requires Yubikey-5). We assume that
the credentials have not yet been changed and thus are:
@table @asis
@item Authentication key
This is a 24 byte key described by the hex string @*
@code{010203040506070801020304050607080102030405060708}.
@item PIV Application PIN
This is the string @code{123456}.
@item PIN Unblocking Key
This is the string @code{12345678}.
@end table
See the example section on how to change these defaults. For
production use it is important to use secure values for them. Note that
the Authentication Key is not queried via the usual Pinentry dialog
but needs to be entered manually or read from a file. The use of a
dedicated machine to personalize tokens is strongly suggested.
To see what is on the card, the command @code{list} can be given. We
will use the interactive mode in the following (the string
@emph{gpg/card>} is the prompt). An example output for a fresh card
is:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: D2760001240102010006090746250000
Application type .: OpenPGP
Version ..........: 2.1
[...]
@end example
It can be seen by the ``Application type'' line that GnuPG selected
the OpenPGP application of the Yubikey. This is because GnuPG assigns
the highest priority to the OpenPGP application. To use the PIV
application of the Yubikey several methods can be used:
With a Yubikey 5 or later the OpenPGP application on the Yubikey can
be disabled:
@example
gpg/card> yubikey disable all opgp
gpg/card> yubikey list
Application USB NFC
-----------------------
OTP yes yes
U2F yes yes
OPGP no no
PIV yes no
OATH yes yes
FIDO2 yes yes
gpg/card> reset
@end example
The @code{reset} is required so that the GnuPG system rereads the
card. Note that disabled applications keep all their data and can at
any time be re-enabled (use @kbd{help yubikey}).
Another option, which works for all Yubikey versions, is to disable
the support for OpenPGP cards in scdaemon. This is done by adding the
line
@smallexample
disable-application openpgp
@end smallexample
to @file{~/.gnupg/scdaemon.conf} and by restarting scdaemon, either by
killing the process or by using @kbd{gpgconf --kill scdaemon}. Finally
the default order in which card applications are tried by scdaemon can
be changed. For example to prefer PIV over OpenPGP it is sufficient
to add
@smallexample
application-priority piv
@end smallexample
to @file{~/.gnupg/scdaemon.conf} and to restart @command{scdaemon}.
This has an effect only on tokens which support both, PIV and OpenPGP,
but does not hamper the use of OpenPGP only tokens.
With one of these methods employed the @code{list} command of
@command{gpg-card} shows this:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: [none]
keyref .....: PIV.9A
Card authenticat. : [none]
keyref .....: PIV.9E
Digital signature : [none]
keyref .....: PIV.9C
Key management ...: [none]
keyref .....: PIV.9D
@end example
In case several tokens are plugged into the computer, gpg-card will
show only one. To show another token the number of the token (0, 1,
2, ...) can be given as an argument to the @code{list} command. The
command @kbd{list --cards} prints a list of all inserted tokens.
Note that the ``Displayed s/n'' is printed on the token and also
shown in Pinentry prompts asking for the PIN. The four standard key
slots are always shown, if other key slots are initialized they are
shown as well. The @emph{PIV authentication} key (internal reference
@emph{PIV.9A}) is used to authenticate the card and the card holder.
The use of the associated private key is protected by the Application
PIN which needs to be provided once and the key can the be used until
the card is reset or removed from the reader or USB port. GnuPG uses
this key with its @emph{Secure Shell} support. The @emph{Card
authentication} key (@emph{PIV.9E}) is also known as the CAK and used
to support physical access applications. The private key is not
protected by a PIN and can thus immediately be used. The @emph{Digital
signature} key (@emph{PIV.9C}) is used to digitally sign documents.
The use of the associated private key is protected by the Application
PIN which needs to be provided for each signing operation. The
@emph{Key management} key (@emph{PIV.9D}) is used for encryption. The
use of the associated private key is protected by the Application PIN
which needs to be provided only once so that decryption operations can
then be done until the card is reset or removed from the reader or USB
port.
We now generate three of the four keys. Note that GnuPG does
currently not use the the @emph{Card authentication} key; however,
that key is mandatory by the PIV standard and thus we create it too.
Key generation requires that we authenticate to the card. This can be
done either on the command line (which would reveal the key):
@example
gpg/card> auth 010203040506070801020304050607080102030405060708
@end example
or by reading the key from a file. That file needs to consist of one
LF terminated line with the hex encoded key (as above):
@example
gpg/card> auth < myauth.key
@end example
As usual @samp{help auth} gives help for this command. An error
message is printed if a non-matching key is used. The authentication
is valid until a reset of the card or until the card is removed from
the reader or the USB port. Note that that in non-interactive mode
the @samp{<} needs to be quoted so that the shell does not interpret
it as a its own redirection symbol.
@noindent
Here are the actual commands to generate the keys:
@example
gpg/card> generate --algo=nistp384 PIV.9A
PIV card no. yk-9074625 detected
gpg/card> generate --algo=nistp256 PIV.9E
PIV card no. yk-9074625 detected
gpg/card> generate --algo=rsa2048 PIV.9C
PIV card no. yk-9074625 detected
@end example
If a key has already been created for one of the slots an error will
be printed; to create a new key anyway the option @samp{--force} can be
used. Note that only the private and public keys have been created
but no certificates are stored in the key slots. In fact, GnuPG uses
its own non-standard method to store just the public key in place of
the the certificate. Other application will not be able to make use
these keys until @command{gpgsm} or another tool has been used to
create and store the respective certificates. Let us see what the
list command now shows:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
Key management ...: [none]
keyref .....: PIV.9D
@end example
The primary information for each key is the @emph{keygrip}, a 40 byte
hex-string identifying the key. This keygrip is a unique identifier
for the specific parameters of a key. It is used by
@command{gpg-agent} and other parts of GnuPG to associate a private
key to its protocol specific certificate format (X.509, OpenPGP, or
SecureShell). Below the keygrip the key reference along with the key
usage capabilities are show. Finally the algorithm is printed in the
format used by @command {gpg}. At that point no other information is
shown because for these new keys gpg won't be able to find matching
certificates.
Although we could have created the @emph{Key management} key also with
the generate command, we will create that key off-card so that a
backup exists. To accomplish this a key needs to be created with
either @command{gpg} or @command{gpgsm} or imported in one of these
tools. In our example we create a self-signed X.509 certificate (exit
the gpg-card tool, first):
@example
$ gpgsm --gen-key -o encr.crt
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 1
What keysize do you want? (3072) 2048
Requested keysize is 2048 bits
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 3
Enter the X.509 subject name: CN=Encryption key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N) y
These parameters are used:
Key-Type: RSA
Key-Length: 2048
Key-Usage: encrypt
Serial: random
Name-DN: CN=Encryption key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N)
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpgsm: certificate created
Ready.
$ gpgsm --import encr.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
Note the last step which imported the created certificate. If you
you instead created a certificate signing request (CSR) instead of a
self-signed certificate and sent this off to a CA you would do the
same import step with the certificate received from the CA. Take note
of the keygrip (prefixed with an ampersand) as shown during the
certificate creation or listed it again using @samp{gpgsm
--with-keygrip -k otto@@example.net}. Now to move the key and
certificate to the card start @command{gpg-card} again and enter:
@example
gpg/card> writekey PIV.9D 34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpg/card> writecert PIV.9D < encr.crt
@end example
If you entered a passphrase to protect the private key, you will be
asked for it via the Pinentry prompt. On success the key and the
certificate has been written to the card and a @code{list} command
shows:
@example
[...]
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
In case the same key (identified by the keygrip) has been used for
several certificates you will see several ``used for'' parts. With
this the encryption key is now fully functional and can be used to
decrypt messages encrypted to this certificate. @sc{Take care:} the
original key is still stored on-disk and should be moved to a backup
medium. This can simply be done by copying the file
@file{34798AAFE0A7565088101CC4AE31C5C8C74461CB.key} from the directory
@file{~/.gnupg/private-keys-v1.d/} to the backup medium and deleting
the file at its original place.
The final example is to create a self-signed certificate for digital
signatures. Leave @command{gpg-card} using @code{quit} or by pressing
Control-D and use gpgsm:
@example
$ gpgsm --learn
$ gpgsm --gen-key -o sign.crt
Please select what kind of key you want:
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 3
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048
Your selection? 3
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 2
Enter the X.509 subject name: CN=Signing key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N)
These parameters are used:
Key-Type: card:PIV.9C
Key-Length: 1024
Key-Usage: sign
Serial: random
Name-DN: CN=Signing key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N) y
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &32A6C6FAFCB8421878608AAB452D5470DD3223ED
gpgsm: certificate created
Ready.
$ gpgsm --import sign.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
The use of @samp{gpgsm --learn} is currently necessary so that
gpg-agent knows what keys are available on the card. The need for
this command will eventually be removed. The remaining commands are
similar to the creation of an on-disk key. However, here we select
the @samp{Digital signature} key. During the creation process you
will be asked for the Application PIN of the card. The final step is
to write the certificate to the card using @command{gpg-card}:
@example
gpg/card> writecert PIV.9C < sign.crt
@end example
By running list again we will see the fully initialized card:
@example
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - [verified] -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Signing key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
It is now possible to sign and to encrypt with this card using gpgsm
and to use the @samp{PIV authentication} key with ssh:
@example
$ ssh-add -l
384 SHA256:0qnJ0Y0ehWxKcx2frLfEljf6GCdlO55OZed5HqGHsaU cardno:yk-9074625 (ECDSA)
@end example
As usual use ssh-add with the uppercase @samp{-L} to list the public
ssh key. To use the certificates with Thunderbird or Mozilla, please
consult the Scute manual for details.
If you want to use the same PIV keys also for OpenPGP (for example on
a Yubikey to avoid switching between OpenPGP and PIV), this is also
possible:
@example
$ gpgsm --learn
$ gpg --full-gen-key
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 14
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
Your selection? 3
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name:
Email address: otto@@example.net
Comment:
You selected this USER-ID:
"otto@@example.net"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
gpg: key C3AFA9ED971BB365 marked as ultimately trusted
gpg: revocation certificate stored as '[...]D971BB365.rev'
public and secret key created and signed.
Note that this key cannot be used for encryption. You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub rsa2048 2019-04-04 [SC]
7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
uid otto@@example.net
@end example
Note that you will be asked two times to enter the PIN of your PIV
card. If you run @command{gpg} in @option{--expert} mode you will
also ge given the option to change the usage flags of the key. The next
typescript shows how to add the encryption subkey:
@example
$ gpg --edit-key 7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
Secret key is available.
sec rsa2048/C3AFA9ED971BB365
created: 2019-04-04 expires: never usage: SC
card-no: FF020001008A77C1
trust: ultimate validity: ultimate
[ultimate] (1). otto@@example.net
gpg> addkey
Secret parts of primary key are stored on-card.
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(14) Existing key from card
Your selection? 14
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
Your selection? 4
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
sec rsa2048/C3AFA9ED971BB365
created: 2019-04-04 expires: never usage: SC
card-no: FF020001008A77C1
trust: ultimate validity: ultimate
ssb rsa2048/7067860A98FCE6E1
created: 2019-04-04 expires: never usage: E
card-no: FF020001008A77C1
[ultimate] (1). otto@@example.net
gpg> save
@end example
Now you can use your PIV card also with @command{gpg}.
@c @mansect examples
@mansect see also
@ifset isman
@command{scdaemon}(1)
@end ifset
diff --git a/doc/gpg.texi b/doc/gpg.texi
index 13d290bec..988835d22 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -1,4341 +1,4341 @@
@c Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
@c 2008, 2009, 2010 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking GPG
@chapter Invoking GPG
@cindex GPG command options
@cindex command options
@cindex options, GPG command
@c Begin standard stuff
@ifclear gpgtwohack
@manpage gpg.1
@ifset manverb
.B gpg
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifclear
@c End standard stuff
@c Begin gpg2 hack stuff
@ifset gpgtwohack
@manpage gpg2.1
@ifset manverb
.B gpg2
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg2
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifset
@c End gpg2 hack stuff
@mansect description
@command{@gpgname} is the OpenPGP part of the GNU Privacy Guard (GnuPG). It
is a tool to provide digital encryption and signing services using the
OpenPGP standard. @command{@gpgname} features complete key management and
all the bells and whistles you would expect from a full OpenPGP
implementation.
There are two main versions of GnuPG: GnuPG 1.x and GnuPG 2.x. GnuPG
2.x supports modern encryption algorithms and thus should be preferred
over GnuPG 1.x. You only need to use GnuPG 1.x if your platform
doesn't support GnuPG 2.x, or you need support for some features that
GnuPG 2.x has deprecated, e.g., decrypting data created with PGP-2
keys.
@ifclear gpgtwohack
If you are looking for version 1 of GnuPG, you may find that version
installed under the name @command{gpg1}.
@end ifclear
@ifset gpgtwohack
In contrast to the standalone command @command{gpg} from GnuPG 1.x,
the 2.x version is commonly installed under the name
@command{@gpgname}.
@end ifset
@manpause
@xref{Option Index}, for an index to @command{@gpgname}'s commands and options.
@mancont
@menu
* GPG Commands:: List of all commands.
* GPG Options:: List of all options.
* GPG Configuration:: Configuration files.
* GPG Examples:: Some usage examples.
Developer information:
* Unattended Usage of GPG:: Using @command{gpg} from other programs.
@end menu
@c * GPG Protocol:: The protocol the server mode uses.
@c *******************************************
@c *************** ****************
@c *************** COMMANDS ****************
@c *************** ****************
@c *******************************************
@mansect commands
@node GPG Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed. Generally speaking, irrelevant options
are silently ignored, and may not be checked for correctness.
@command{@gpgname} may be run with no commands. In this case it will
print a warning perform a reasonable action depending on the type of
file it is given as input (an encrypted message is decrypted, a
signature is verified, a file containing keys is listed, etc.).
If you run into any problems, please add the option @option{--verbose}
to the invocation to see more diagnostics.
@menu
* General GPG Commands:: Commands not specific to the functionality.
* Operational GPG Commands:: Commands to select the type of operation.
* OpenPGP Key Management:: How to manage your keys.
@end menu
@c *******************************************
@c ********** GENERAL COMMANDS *************
@c *******************************************
@node General GPG Commands
@subsection Commands not specific to the function
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you
cannot abbreviate this command.
@item --help
@itemx -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot arbitrarily abbreviate this command
(though you can use its short form @option{-h}).
@item --warranty
@opindex warranty
Print warranty information.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@end table
@c *******************************************
@c ******** OPERATIONAL COMMANDS ***********
@c *******************************************
@node Operational GPG Commands
@subsection Commands to select the type of operation
@table @gnupgtabopt
@item --sign
@itemx -s
@opindex sign
Sign a message. This command may be combined with @option{--encrypt}
(to sign and encrypt a message), @option{--symmetric} (to sign and
symmetrically encrypt a message), or both @option{--encrypt} and
@option{--symmetric} (to sign and encrypt a message that can be
decrypted using a secret key or a passphrase). The signing key is
chosen by default or can be set explicitly using the
@option{--local-user} and @option{--default-key} options.
@item --clear-sign
@opindex clear-sign
@itemx --clearsign
@opindex clearsign
Make a cleartext signature. The content in a cleartext signature is
readable without any special software. OpenPGP software is only needed
to verify the signature. cleartext signatures may modify end-of-line
whitespace for platform independence and are not intended to be
reversible. The signing key is chosen by default or can be set
explicitly using the @option{--local-user} and @option{--default-key}
options.
@item --detach-sign
@itemx -b
@opindex detach-sign
Make a detached signature.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt data to one or more public keys. This command may be combined
with @option{--sign} (to sign and encrypt a message),
@option{--symmetric} (to encrypt a message that can be decrypted using a
secret key or a passphrase), or @option{--sign} and
@option{--symmetric} together (for a signed message that can be
decrypted using a secret key or a passphrase). @option{--recipient}
and related options specify which public keys to use for encryption.
@item --symmetric
@itemx -c
@opindex symmetric
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option. This command may be combined with
@option{--sign} (for a signed and symmetrically encrypted message),
@option{--encrypt} (for a message that may be decrypted via a secret key
or a passphrase), or @option{--sign} and @option{--encrypt} together
(for a signed message that may be decrypted via a secret key or a
passphrase). @command{@gpgname} caches the passphrase used for
symmetric encryption so that a decrypt operation may not require that
the user needs to enter the passphrase. The option
@option{--no-symkey-cache} can be used to disable this feature.
@item --store
@opindex store
Store only (make a simple literal data packet).
@item --decrypt
@itemx -d
@opindex decrypt
Decrypt the file given on the command line (or STDIN if no file
is specified) and write it to STDOUT (or the file specified with
@option{--output}). If the decrypted file is signed, the signature is also
verified. This command differs from the default operation, as it never
writes to the filename which is included in the file and it rejects
files that don't begin with an encrypted message.
@item --verify
@opindex verify
Assume that the first argument is a signed file and verify it without
generating any output. With no arguments, the signature packet is
read from STDIN. If only one argument is given, the specified file is
expected to include a complete signature.
With more than one argument, the first argument should specify a file
with a detached signature and the remaining files should contain the
signed data. To read the signed data from STDIN, use @samp{-} as the
second filename. For security reasons, a detached signature will not
read the signed material from STDIN if not explicitly specified.
Note: If the option @option{--batch} is not used, @command{@gpgname}
may assume that a single argument is a file with a detached signature,
and it will try to find a matching data file by stripping certain
suffixes. Using this historical feature to verify a detached
signature is strongly discouraged; you should always specify the data file
explicitly.
Note: When verifying a cleartext signature, @command{@gpgname} verifies
only what makes up the cleartext signed data and not any extra data
outside of the cleartext signature or the header lines directly following
the dash marker line. The option @code{--output} may be used to write
out the actual signed data, but there are other pitfalls with this
format as well. It is suggested to avoid cleartext signatures in
favor of detached signatures.
Note: Sometimes the use of the @command{gpgv} tool is easier than
using the full-fledged @command{gpg} with this option. @command{gpgv}
is designed to compare signed data against a list of trusted keys and
returns with success only for a good signature. It has its own manual
page.
@item --multifile
@opindex multifile
This modifies certain other commands to accept multiple files for
processing on the command line or read from STDIN with each filename on
a separate line. This allows for many files to be processed at
once. @option{--multifile} may currently be used along with
@option{--verify}, @option{--encrypt}, and @option{--decrypt}. Note that
@option{--multifile --verify} may not be used with detached signatures.
@item --verify-files
@opindex verify-files
Identical to @option{--multifile --verify}.
@item --encrypt-files
@opindex encrypt-files
Identical to @option{--multifile --encrypt}.
@item --decrypt-files
@opindex decrypt-files
Identical to @option{--multifile --decrypt}.
@item --list-keys
@itemx -k
@itemx --list-public-keys
@opindex list-keys
List the specified keys. If no keys are specified, then all keys from
the configured public keyrings are listed.
Never use the output of this command in scripts or other programs.
The output is intended only for humans and its format is likely to
change. The @option{--with-colons} option emits the output in a
stable, machine-parseable format, which is intended for use by scripts
and other programs.
@item --list-secret-keys
@itemx -K
@opindex list-secret-keys
List the specified secret keys. If no keys are specified, then all
known secret keys are listed. A @code{#} after the initial tags
@code{sec} or @code{ssb} means that the secret key or subkey is
currently not usable. We also say that this key has been taken
offline (for example, a primary key can be taken offline by exporting
the key using the command @option{--export-secret-subkeys}). A
@code{>} after these tags indicate that the key is stored on a
smartcard. See also @option{--list-keys}.
@item --check-signatures
@opindex check-signatures
@itemx --check-sigs
@opindex check-sigs
Same as @option{--list-keys}, but the key signatures are verified and
listed too. Note that for performance reasons the revocation status
of a signing key is not shown. This command has the same effect as
using @option{--list-keys} with @option{--with-sig-check}.
The status of the verification is indicated by a flag directly
following the "sig" tag (and thus before the flags described below. A
"!" indicates that the signature has been successfully verified, a "-"
denotes a bad signature and a "%" is used if an error occurred while
checking the signature (e.g. a non supported algorithm). Signatures
where the public key is not available are not listed; to see their
keyids the command @option{--list-sigs} can be used.
For each signature listed, there are several flags in between the
signature status flag and keyid. These flags give additional
information about each key signature. From left to right, they are
the numbers 1-3 for certificate check level (see
@option{--ask-cert-level}), "L" for a local or non-exportable
signature (see @option{--lsign-key}), "R" for a nonRevocable signature
(see the @option{--edit-key} command "nrsign"), "P" for a signature
that contains a policy URL (see @option{--cert-policy-url}), "N" for a
signature that contains a notation (see @option{--cert-notation}), "X"
for an eXpired signature (see @option{--ask-cert-expire}), and the
numbers 1-9 or "T" for 10 and above to indicate trust signature levels
(see the @option{--edit-key} command "tsign").
@item --locate-keys
@itemx --locate-external-keys
@opindex locate-keys
@opindex locate-external-keys
Locate the keys given as arguments. This command basically uses the
same algorithm as used when locating keys for encryption and may thus
be used to see what keys @command{@gpgname} might use. In particular
external methods as defined by @option{--auto-key-locate} may be used
to locate a key. Only public keys are listed. The variant
@option{--locate-external-keys} does not consider a locally existing
key and can thus be used to force the refresh of a key via the defined
external methods.
@item --show-keys
@opindex show-keys
This commands takes OpenPGP keys as input and prints information about
them in the same way the command @option{--list-keys} does for locally
stored key. In addition the list options @code{show-unusable-uids},
@code{show-unusable-subkeys}, @code{show-notations} and
@code{show-policy-urls} are also enabled. As usual for automated
processing, this command should be combined with the option
@option{--with-colons}.
@item --fingerprint
@opindex fingerprint
List all keys (or the specified ones) along with their
fingerprints. This is the same output as @option{--list-keys} but with
the additional output of a line with the fingerprint. May also be
combined with @option{--check-signatures}. If this
command is given twice, the fingerprints of all secondary keys are
listed too. This command also forces pretty printing of fingerprints
if the keyid format has been set to "none".
@item --list-packets
@opindex list-packets
List only the sequence of packets. This command is only useful for
debugging. When used with option @option{--verbose} the actual MPI
values are dumped and not only their lengths. Note that the output of
this command may change with new releases.
@item --edit-card
@opindex edit-card
@itemx --card-edit
@opindex card-edit
Present a menu to work with a smartcard. The subcommand "help" provides
an overview on available commands. For a detailed description, please
see the Card HOWTO at
https://gnupg.org/documentation/howtos.html#GnuPG-cardHOWTO .
@item --card-status
@opindex card-status
Show the content of the smart card.
@item --change-pin
@opindex change-pin
Present a menu to allow changing the PIN of a smartcard. This
functionality is also available as the subcommand "passwd" with the
@option{--edit-card} command.
@item --delete-keys @var{name}
@opindex delete-keys
Remove key from the public keyring. In batch mode either @option{--yes} is
required or the key must be specified by fingerprint. This is a
safeguard against accidental deletion of multiple keys. If the
exclamation mark syntax is used with the fingerprint of a subkey only
that subkey is deleted; if the exclamation mark is used with the
fingerprint of the primary key the entire public key is deleted.
@item --delete-secret-keys @var{name}
@opindex delete-secret-keys
Remove key from the secret keyring. In batch mode the key must be
specified by fingerprint. The option @option{--yes} can be used to
advice gpg-agent not to request a confirmation. This extra
pre-caution is done because @command{@gpgname} can't be sure that the
secret key (as controlled by gpg-agent) is only used for the given
OpenPGP public key. If the exclamation mark syntax is used with the
fingerprint of a subkey only the secret part of that subkey is
deleted; if the exclamation mark is used with the fingerprint of the
primary key only the secret part of the primary key is deleted.
@item --delete-secret-and-public-key @var{name}
@opindex delete-secret-and-public-key
Same as @option{--delete-key}, but if a secret key exists, it will be
removed first. In batch mode the key must be specified by fingerprint.
The option @option{--yes} can be used to advice gpg-agent not to
request a confirmation.
@item --export
@opindex export
Either export all keys from all keyrings (default keyrings and those
registered via option @option{--keyring}), or if at least one name is given,
those of the given name. The exported keys are written to STDOUT or to the
file given with option @option{--output}. Use together with
@option{--armor} to mail those keys.
@item --send-keys @var{keyIDs}
@opindex send-keys
Similar to @option{--export} but sends the keys to a keyserver.
Fingerprints may be used instead of key IDs.
Don't send your complete keyring to a keyserver --- select
only those keys which are new or changed by you. If no @var{keyIDs}
are given, @command{@gpgname} does nothing.
@item --export-secret-keys
@itemx --export-secret-subkeys
@opindex export-secret-keys
@opindex export-secret-subkeys
Same as @option{--export}, but exports the secret keys instead. The
exported keys are written to STDOUT or to the file given with option
@option{--output}. This command is often used along with the option
@option{--armor} to allow for easy printing of the key for paper backup;
however the external tool @command{paperkey} does a better job of
creating backups on paper. Note that exporting a secret key can be a
security risk if the exported keys are sent over an insecure channel.
The second form of the command has the special property to render the
secret part of the primary key useless; this is a GNU extension to
OpenPGP and other implementations can not be expected to successfully
import such a key. Its intended use is in generating a full key with
an additional signing subkey on a dedicated machine. This command
then exports the key without the primary key to the main machine.
GnuPG may ask you to enter the passphrase for the key. This is
required, because the internal protection method of the secret key is
different from the one specified by the OpenPGP protocol.
@item --export-ssh-key
@opindex export-ssh-key
This command is used to export a key in the OpenSSH public key format.
It requires the specification of one key by the usual means and
exports the latest valid subkey which has an authentication capability
to STDOUT or to the file given with option @option{--output}. That
output can directly be added to ssh's @file{authorized_key} file.
By specifying the key to export using a key ID or a fingerprint
suffixed with an exclamation mark (!), a specific subkey or the
primary key can be exported. This does not even require that the key
has the authentication capability flag set.
@item --import
@itemx --fast-import
@opindex import
Import/merge keys. This adds the given keys to the
keyring. The fast version is currently just a synonym.
There are a few other options which control how this command works.
Most notable here is the @option{--import-options merge-only} option
which does not insert new keys but does only the merging of new
signatures, user-IDs and subkeys.
@item --receive-keys @var{keyIDs}
@opindex receive-keys
@itemx --recv-keys @var{keyIDs}
@opindex recv-keys
Import the keys with the given @var{keyIDs} from a keyserver.
@item --refresh-keys
@opindex refresh-keys
Request updates from a keyserver for keys that already exist on the
local keyring. This is useful for updating a key with the latest
signatures, user IDs, etc. Calling this with no arguments will refresh
the entire keyring.
@item --search-keys @var{names}
@opindex search-keys
Search the keyserver for the given @var{names}. Multiple names given
here will be joined together to create the search string for the
keyserver. Note that keyservers search for @var{names} in a different
and simpler way than gpg does. The best choice is to use a mail
address. Due to data privacy reasons keyservers may even not even
allow searching by user id or mail address and thus may only return
results when being used with the @option{--recv-key} command to
search by key fingerprint or keyid.
@item --fetch-keys @var{URIs}
@opindex fetch-keys
Retrieve keys located at the specified @var{URIs}. Note that different
installations of GnuPG may support different protocols (HTTP, FTP,
LDAP, etc.). When using HTTPS the system provided root certificates
are used by this command.
@item --update-trustdb
@opindex update-trustdb
Do trust database maintenance. This command iterates over all keys and
builds the Web of Trust. This is an interactive command because it may
have to ask for the "ownertrust" values for keys. The user has to give
an estimation of how far she trusts the owner of the displayed key to
correctly certify (sign) other keys. GnuPG only asks for the ownertrust
value if it has not yet been assigned to a key. Using the
@option{--edit-key} menu, the assigned value can be changed at any time.
@item --check-trustdb
@opindex check-trustdb
Do trust database maintenance without user interaction. From time to
time the trust database must be updated so that expired keys or
signatures and the resulting changes in the Web of Trust can be
tracked. Normally, GnuPG will calculate when this is required and do it
automatically unless @option{--no-auto-check-trustdb} is set. This
command can be used to force a trust database check at any time. The
processing is identical to that of @option{--update-trustdb} but it
skips keys with a not yet defined "ownertrust".
For use with cron jobs, this command can be used together with
@option{--batch} in which case the trust database check is done only if
a check is needed. To force a run even in batch mode add the option
@option{--yes}.
@anchor{option --export-ownertrust}
@item --export-ownertrust
@opindex export-ownertrust
Send the ownertrust values to STDOUT. This is useful for backup purposes
as these values are the only ones which can't be re-created from a
corrupted trustdb. Example:
@c man:.RS
@example
@gpgname{} --export-ownertrust > otrust.txt
@end example
@c man:.RE
@item --import-ownertrust
@opindex import-ownertrust
Update the trustdb with the ownertrust values stored in @code{files} (or
STDIN if not given); existing values will be overwritten. In case of a
severely damaged trustdb and if you have a recent backup of the
ownertrust values (e.g. in the file @file{otrust.txt}), you may re-create
the trustdb using these commands:
@c man:.RS
@example
cd ~/.gnupg
rm trustdb.gpg
@gpgname{} --import-ownertrust < otrust.txt
@end example
@c man:.RE
@item --rebuild-keydb-caches
@opindex rebuild-keydb-caches
When updating from version 1.0.6 to 1.0.7 this command should be used
to create signature caches in the keyring. It might be handy in other
situations too.
@item --print-md @var{algo}
@itemx --print-mds
@opindex print-md
Print message digest of algorithm @var{algo} for all given files or STDIN.
With the second form (or a deprecated "*" for @var{algo}) digests for all
available algorithms are printed.
@item --gen-random @var{0|1|2} @var{count}
@opindex gen-random
Emit @var{count} random bytes of the given quality level 0, 1 or 2. If
@var{count} is not given or zero, an endless sequence of random bytes
will be emitted. If used with @option{--armor} the output will be
base64 encoded. PLEASE, don't use this command unless you know what
you are doing; it may remove precious entropy from the system!
@item --gen-prime @var{mode} @var{bits}
@opindex gen-prime
Use the source, Luke :-). The output format is subject to change
with ant release.
@item --enarmor
@itemx --dearmor
@opindex enarmor
@opindex dearmor
Pack or unpack an arbitrary input into/from an OpenPGP ASCII armor.
This is a GnuPG extension to OpenPGP and in general not very useful.
@item --unwrap
@opindex unwrap
This command is similar to @option{--decrypt} with the change that the
output is not the usual plaintext but the original message with the
decryption layer removed. Thus the output will be an OpenPGP data
structure which often means a signed OpenPGP message. Note that this
command may or may not remove a compression layer which is often found
beneath the encryption layer.
@item --tofu-policy @{auto|good|unknown|bad|ask@} @var{keys}
@opindex tofu-policy
Set the TOFU policy for all the bindings associated with the specified
@var{keys}. For more information about the meaning of the policies,
@pxref{trust-model-tofu}. The @var{keys} may be specified either by their
fingerprint (preferred) or their keyid.
@c @item --server
@c @opindex server
@c Run gpg in server mode. This feature is not yet ready for use and
@c thus not documented.
@end table
@c ********************************************
@c ******* KEY MANAGEMENT COMMANDS **********
@c ********************************************
@node OpenPGP Key Management
@subsection How to manage your keys
This section explains the main commands for key management.
@table @gnupgtabopt
@item --quick-generate-key @var{user-id} [@var{algo} [@var{usage} [@var{expire}]]]
@itemx --quick-gen-key
@opindex quick-generate-key
@opindex quick-gen-key
This is a simple command to generate a standard key with one user id.
In contrast to @option{--generate-key} the key is generated directly
without the need to answer a bunch of prompts. Unless the option
@option{--yes} is given, the key creation will be canceled if the
given user id already exists in the keyring.
If invoked directly on the console without any special options an
answer to a ``Continue?'' style confirmation prompt is required. In
case the user id already exists in the keyring a second prompt to
force the creation of the key will show up.
If @var{algo} or @var{usage} are given, only the primary key is
created and no prompts are shown. To specify an expiration date but
still create a primary and subkey use ``default'' or
``future-default'' for @var{algo} and ``default'' for @var{usage}.
For a description of these optional arguments see the command
@code{--quick-add-key}. The @var{usage} accepts also the value
``cert'' which can be used to create a certification only primary key;
the default is to a create certification and signing key.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
If this command is used with @option{--batch},
@option{--pinentry-mode} has been set to @code{loopback}, and one of
the passphrase options (@option{--passphrase},
@option{--passphrase-fd}, or @option{passphrase-file}) is used, the
supplied passphrase is used for the new key and the agent does not ask
for it. To create a key without any protection @code{--passphrase ''}
may be used.
To create an OpenPGP key from the keys available on the currently
inserted smartcard, the special string ``card'' can be used for
@var{algo}. If the card features an encryption and a signing key, gpg
will figure them out and creates an OpenPGP key consisting of the
usual primary key and one subkey. This works only with certain
smartcards. Note that the interactive @option{--full-gen-key} command
allows to do the same but with greater flexibility in the selection of
the smartcard keys.
Note that it is possible to create a primary key and a subkey using
non-default algorithms by using ``default'' and changing the default
parameters using the option @option{--default-new-key-algo}.
@item --quick-set-expire @var{fpr} @var{expire} [*|@var{subfprs}]
@opindex quick-set-expire
With two arguments given, directly set the expiration time of the
primary key identified by @var{fpr} to @var{expire}. To remove the
expiration time @code{0} can be used. With three arguments and the
third given as an asterisk, the expiration time of all non-revoked and
not yet expired subkeys are set to @var{expire}. With more than two
arguments and a list of fingerprints given for @var{subfprs}, all
non-revoked subkeys matching these fingerprints are set to
@var{expire}.
@item --quick-add-key @var{fpr} [@var{algo} [@var{usage} [@var{expire}]]]
@opindex quick-add-key
Directly add a subkey to the key identified by the fingerprint
@var{fpr}. Without the optional arguments an encryption subkey is
added. If any of the arguments are given a more specific subkey is
added.
@var{algo} may be any of the supported algorithms or curve names
given in the format as used by key listings. To use the default
algorithm the string ``default'' or ``-'' can be used. Supported
algorithms are ``rsa'', ``dsa'', ``elg'', ``ed25519'', ``cv25519'',
and other ECC curves. For example the string ``rsa'' adds an RSA key
with the default key length; a string ``rsa4096'' requests that the
key length is 4096 bits. The string ``future-default'' is an alias
for the algorithm which will likely be used as default algorithm in
future versions of gpg. To list the supported ECC curves the command
@code{gpg --with-colons --list-config curve} can be used.
Depending on the given @var{algo} the subkey may either be an
encryption subkey or a signing subkey. If an algorithm is capable of
signing and encryption and such a subkey is desired, a @var{usage}
string must be given. This string is either ``default'' or ``-'' to
keep the default or a comma delimited list (or space delimited list)
of keywords: ``sign'' for a signing subkey, ``auth'' for an
authentication subkey, and ``encr'' for an encryption subkey
(``encrypt'' can be used as alias for ``encr''). The valid
combinations depend on the algorithm.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
@item --generate-key
@opindex generate-key
@itemx --gen-key
@opindex gen-key
Generate a new key pair using the current default parameters. This is
the standard command to create a new key. In addition to the key a
revocation certificate is created and stored in the
@file{openpgp-revocs.d} directory below the GnuPG home directory.
@item --full-generate-key
@opindex full-generate-key
@itemx --full-gen-key
@opindex full-gen-key
Generate a new key pair with dialogs for all options. This is an
extended version of @option{--generate-key}.
There is also a feature which allows you to create keys in batch
mode. See the manual section ``Unattended key generation'' on how
to use this.
@item --generate-revocation @var{name}
@opindex generate-revocation
@itemx --gen-revoke @var{name}
@opindex gen-revoke
Generate a revocation certificate for the complete key. To only revoke
a subkey or a key signature, use the @option{--edit} command.
This command merely creates the revocation certificate so that it can
be used to revoke the key if that is ever needed. To actually revoke
a key the created revocation certificate needs to be merged with the
key to revoke. This is done by importing the revocation certificate
using the @option{--import} command. Then the revoked key needs to be
published, which is best done by sending the key to a keyserver
(command @option{--send-key}) and by exporting (@option{--export}) it
to a file which is then send to frequent communication partners.
@item --generate-designated-revocation @var{name}
@opindex generate-designated-revocation
@itemx --desig-revoke @var{name}
@opindex desig-revoke
Generate a designated revocation certificate for a key. This allows a
user (with the permission of the keyholder) to revoke someone else's
key.
@item --edit-key
@opindex edit-key
Present a menu which enables you to do most of the key management
related tasks. It expects the specification of a key on the command
line.
@c ******** Begin Edit-key Options **********
@table @asis
@item uid @var{n}
@opindex keyedit:uid
Toggle selection of user ID or photographic user ID with index @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item key @var{n}
@opindex keyedit:key
Toggle selection of subkey with index @var{n} or key ID @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item sign
@opindex keyedit:sign
Make a signature on key of user @code{name}. If the key is not yet
signed by the default user (or the users given with @option{-u}), the program
displays the information of the key again, together with its
fingerprint and asks whether it should be signed. This question is
repeated for all users specified with
@option{-u}.
@item lsign
@opindex keyedit:lsign
Same as "sign" but the signature is marked as non-exportable and will
therefore never be used by others. This may be used to make keys
valid only in the local environment.
@item nrsign
@opindex keyedit:nrsign
Same as "sign" but the signature is marked as non-revocable and can
therefore never be revoked.
@item tsign
@opindex keyedit:tsign
Make a trust signature. This is a signature that combines the notions
of certification (like a regular signature), and trust (like the
"trust" command). It is generally only useful in distinct communities
or groups. For more information please read the sections
``Trust Signature'' and ``Regular Expression'' in RFC-4880.
@end table
@c man:.RS
Note that "l" (for local / non-exportable), "nr" (for non-revocable,
and "t" (for trust) may be freely mixed and prefixed to "sign" to
create a signature of any type desired.
@c man:.RE
If the option @option{--only-sign-text-ids} is specified, then any
non-text based user ids (e.g., photo IDs) will not be selected for
signing.
@table @asis
@item delsig
@opindex keyedit:delsig
Delete a signature. Note that it is not possible to retract a signature,
once it has been send to the public (i.e. to a keyserver). In that case
you better use @code{revsig}.
@item revsig
@opindex keyedit:revsig
Revoke a signature. For every signature which has been generated by
one of the secret keys, GnuPG asks whether a revocation certificate
should be generated.
@item check
@opindex keyedit:check
Check the signatures on all selected user IDs. With the extra
option @code{selfsig} only self-signatures are shown.
@item adduid
@opindex keyedit:adduid
Create an additional user ID.
@item addphoto
@opindex keyedit:addphoto
Create a photographic user ID. This will prompt for a JPEG file that
will be embedded into the user ID. Note that a very large JPEG will make
for a very large key. Also note that some programs will display your
JPEG unchanged (GnuPG), and some programs will scale it to fit in a
dialog box (PGP).
@item showphoto
@opindex keyedit:showphoto
Display the selected photographic user ID.
@item deluid
@opindex keyedit:deluid
Delete a user ID or photographic user ID. Note that it is not
possible to retract a user id, once it has been send to the public
(i.e. to a keyserver). In that case you better use @code{revuid}.
@item revuid
@opindex keyedit:revuid
Revoke a user ID or photographic user ID.
@item primary
@opindex keyedit:primary
Flag the current user id as the primary one, removes the primary user
id flag from all other user ids and sets the timestamp of all affected
self-signatures one second ahead. Note that setting a photo user ID
as primary makes it primary over other photo user IDs, and setting a
regular user ID as primary makes it primary over other regular user
IDs.
@item keyserver
@opindex keyedit:keyserver
Set a preferred keyserver for the specified user ID(s). This allows
other users to know where you prefer they get your key from. See
@option{--keyserver-options honor-keyserver-url} for more on how this
works. Setting a value of "none" removes an existing preferred
keyserver.
@item notation
@opindex keyedit:notation
Set a name=value notation for the specified user ID(s). See
@option{--cert-notation} for more on how this works. Setting a value of
"none" removes all notations, setting a notation prefixed with a minus
sign (-) removes that notation, and setting a notation name (without the
=value) prefixed with a minus sign removes all notations with that name.
@item pref
@opindex keyedit:pref
List preferences from the selected user ID. This shows the actual
preferences, without including any implied preferences.
@item showpref
@opindex keyedit:showpref
More verbose preferences listing for the selected user ID. This shows
the preferences in effect by including the implied preferences of 3DES
(cipher), SHA-1 (digest), and Uncompressed (compression) if they are
not already included in the preference list. In addition, the
preferred keyserver and signature notations (if any) are shown.
@item setpref @var{string}
@opindex keyedit:setpref
Set the list of user ID preferences to @var{string} for all (or just
the selected) user IDs. Calling setpref with no arguments sets the
preference list to the default (either built-in or set via
@option{--default-preference-list}), and calling setpref with "none"
as the argument sets an empty preference list. Use @command{@gpgname
--version} to get a list of available algorithms. Note that while you
can change the preferences on an attribute user ID (aka "photo ID"),
GnuPG does not select keys via attribute user IDs so these preferences
will not be used by GnuPG.
When setting preferences, you should list the algorithms in the order
which you'd like to see them used by someone else when encrypting a
message to your key. If you don't include 3DES, it will be
automatically added at the end. Note that there are many factors that
go into choosing an algorithm (for example, your key may not be the
only recipient), and so the remote OpenPGP application being used to
send to you may or may not follow your exact chosen order for a given
message. It will, however, only choose an algorithm that is present
on the preference list of every recipient key. See also the
INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS section below.
@item addkey
@opindex keyedit:addkey
Add a subkey to this key.
@item addcardkey
@opindex keyedit:addcardkey
Generate a subkey on a card and add it to this key.
@item keytocard
@opindex keyedit:keytocard
Transfer the selected secret subkey (or the primary key if no subkey
has been selected) to a smartcard. The secret key in the keyring will
be replaced by a stub if the key could be stored successfully on the
card and you use the save command later. Only certain key types may be
transferred to the card. A sub menu allows you to select on what card
to store the key. Note that it is not possible to get that key back
from the card - if the card gets broken your secret key will be lost
unless you have a backup somewhere.
@item bkuptocard @var{file}
@opindex keyedit:bkuptocard
Restore the given @var{file} to a card. This command may be used to restore a
backup key (as generated during card initialization) to a new card. In
almost all cases this will be the encryption key. You should use this
command only with the corresponding public key and make sure that the
file given as argument is indeed the backup to restore. You should then
select 2 to restore as encryption key. You will first be asked to enter
the passphrase of the backup key and then for the Admin PIN of the card.
@item delkey
@opindex keyedit:delkey
Remove a subkey (secondary key). Note that it is not possible to retract
a subkey, once it has been send to the public (i.e. to a keyserver). In
that case you better use @code{revkey}. Also note that this only
deletes the public part of a key.
@item revkey
@opindex keyedit:revkey
Revoke a subkey.
@item expire
@opindex keyedit:expire
Change the key or subkey expiration time. If a subkey is selected, the
expiration time of this subkey will be changed. With no selection, the
key expiration of the primary key is changed.
@item trust
@opindex keyedit:trust
Change the owner trust value for the key. This updates the trust-db
immediately and no save is required.
@item disable
@itemx enable
@opindex keyedit:disable
@opindex keyedit:enable
Disable or enable an entire key. A disabled key can not normally be
used for encryption.
@item addrevoker
@opindex keyedit:addrevoker
Add a designated revoker to the key. This takes one optional argument:
"sensitive". If a designated revoker is marked as sensitive, it will
not be exported by default (see export-options).
@item passwd
@opindex keyedit:passwd
Change the passphrase of the secret key.
@item toggle
@opindex keyedit:toggle
This is dummy command which exists only for backward compatibility.
@item clean
@opindex keyedit:clean
Compact (by removing all signatures except the selfsig) any user ID
that is no longer usable (e.g. revoked, or expired). Then, remove any
signatures that are not usable by the trust calculations.
Specifically, this removes any signature that does not validate, any
signature that is superseded by a later signature, revoked signatures,
and signatures issued by keys that are not present on the keyring.
@item minimize
@opindex keyedit:minimize
Make the key as small as possible. This removes all signatures from
each user ID except for the most recent self-signature.
@item change-usage
@opindex keyedit:change-usage
Change the usage flags (capabilities) of the primary key or of
subkeys. These usage flags (e.g. Certify, Sign, Authenticate,
Encrypt) are set during key creation. Sometimes it is useful to
have the opportunity to change them (for example to add
Authenticate) after they have been created. Please take care when
doing this; the allowed usage flags depend on the key algorithm.
@item cross-certify
@opindex keyedit:cross-certify
Add cross-certification signatures to signing subkeys that may not
currently have them. Cross-certification signatures protect against a
subtle attack against signing subkeys. See
@option{--require-cross-certification}. All new keys generated have
this signature by default, so this command is only useful to bring
older keys up to date.
@item save
@opindex keyedit:save
Save all changes to the keyrings and quit.
@item quit
@opindex keyedit:quit
Quit the program without updating the
keyrings.
@end table
@c man:.RS
The listing shows you the key with its secondary keys and all user
IDs. The primary user ID is indicated by a dot, and selected keys or
user IDs are indicated by an asterisk. The trust
value is displayed with the primary key: "trust" is the assigned owner
trust and "validity" is the calculated validity of the key. Validity
values are also displayed for all user IDs.
For possible values of trust, @pxref{trust-values}.
@c man:.RE
@c ******** End Edit-key Options **********
@item --sign-key @var{name}
@opindex sign-key
Signs a public key with your secret key. This is a shortcut version of
the subcommand "sign" from @option{--edit}.
@item --lsign-key @var{name}
@opindex lsign-key
Signs a public key with your secret key but marks it as
non-exportable. This is a shortcut version of the subcommand "lsign"
from @option{--edit-key}.
@item --quick-sign-key @var{fpr} [@var{names}]
@itemx --quick-lsign-key @var{fpr} [@var{names}]
@opindex quick-sign-key
@opindex quick-lsign-key
Directly sign a key from the passphrase without any further user
interaction. The @var{fpr} must be the verified primary fingerprint
of a key in the local keyring. If no @var{names} are given, all
useful user ids are signed; with given [@var{names}] only useful user
ids matching one of these names are signed. By default, or if a name
is prefixed with a '*', a case insensitive substring match is used.
If a name is prefixed with a '=' a case sensitive exact match is done.
The command @option{--quick-lsign-key} marks the signatures as
non-exportable. If such a non-exportable signature already exists the
@option{--quick-sign-key} turns it into a exportable signature.
This command uses reasonable defaults and thus does not provide the
full flexibility of the "sign" subcommand from @option{--edit-key}.
Its intended use is to help unattended key signing by utilizing a list
of verified fingerprints.
@item --quick-add-uid @var{user-id} @var{new-user-id}
@opindex quick-add-uid
This command adds a new user id to an existing key. In contrast to
the interactive sub-command @code{adduid} of @option{--edit-key} the
@var{new-user-id} is added verbatim with only leading and trailing
white space removed, it is expected to be UTF-8 encoded, and no checks
on its form are applied.
@item --quick-revoke-uid @var{user-id} @var{user-id-to-revoke}
@opindex quick-revoke-uid
This command revokes a user ID on an existing key. It cannot be used
to revoke the last user ID on key (some non-revoked user ID must
remain), with revocation reason ``User ID is no longer valid''. If
you want to specify a different revocation reason, or to supply
supplementary revocation text, you should use the interactive
sub-command @code{revuid} of @option{--edit-key}.
@item --quick-set-primary-uid @var{user-id} @var{primary-user-id}
@opindex quick-set-primary-uid
This command sets or updates the primary user ID flag on an existing
key. @var{user-id} specifies the key and @var{primary-user-id} the
user ID which shall be flagged as the primary user ID. The primary
user ID flag is removed from all other user ids and the timestamp of
all affected self-signatures is set one second ahead.
@item --change-passphrase @var{user-id}
@opindex change-passphrase
@itemx --passwd @var{user-id}
@opindex passwd
Change the passphrase of the secret key belonging to the certificate
specified as @var{user-id}. This is a shortcut for the sub-command
@code{passwd} of the edit key menu. When using together with the
option @option{--dry-run} this will not actually change the passphrase
but check that the current passphrase is correct.
@end table
@c *******************************************
@c *************** ****************
@c *************** OPTIONS ****************
@c *************** ****************
@c *******************************************
@mansect options
@node GPG Options
@section Option Summary
@command{@gpgname} features a bunch of options to control the exact
behaviour and to change the default configuration.
@menu
* GPG Configuration Options:: How to change the configuration.
* GPG Key related Options:: Key related options.
* GPG Input and Output:: Input and Output.
* OpenPGP Options:: OpenPGP protocol specific options.
* Compliance Options:: Compliance options.
* GPG Esoteric Options:: Doing things one usually doesn't want to do.
* Deprecated Options:: Deprecated options.
@end menu
Long options can be put in an options file (default
"~/.gnupg/gpg.conf"). Short option names will not work - for example,
"armor" is a valid option for the options file, while "a" is not. Do not
write the 2 dashes, but simply the name of the option and any required
arguments. Lines with a hash ('#') as the first non-white-space
character are ignored. Commands may be put in this file too, but that is
not generally useful as the command will execute automatically with
every execution of gpg.
Please remember that option parsing stops as soon as a non-option is
encountered, you can explicitly stop parsing by using the special option
@option{--}.
@c *******************************************
@c ******** CONFIGURATION OPTIONS **********
@c *******************************************
@node GPG Configuration Options
@subsection How to change the configuration
These options are used to change the configuration and are usually found
in the option file.
@table @gnupgtabopt
@item --default-key @var{name}
@opindex default-key
Use @var{name} as the default key to sign with. If this option is not
used, the default key is the first key found in the secret keyring.
Note that @option{-u} or @option{--local-user} overrides this option.
This option may be given multiple times. In this case, the last key
for which a secret key is available is used. If there is no secret
key available for any of the specified values, GnuPG will not emit an
error message but continue as if this option wasn't given.
@item --default-recipient @var{name}
@opindex default-recipient
Use @var{name} as default recipient if option @option{--recipient} is
not used and don't ask if this is a valid one. @var{name} must be
non-empty.
@item --default-recipient-self
@opindex default-recipient-self
Use the default key as default recipient if option @option{--recipient} is not
used and don't ask if this is a valid one. The default key is the first
one from the secret keyring or the one set with @option{--default-key}.
@item --no-default-recipient
@opindex no-default-recipient
Reset @option{--default-recipient} and @option{--default-recipient-self}.
@item -v, --verbose
@opindex verbose
Give more information during processing. If used
twice, the input data is listed in detail.
@item --no-verbose
@opindex no-verbose
Reset verbose level to 0.
@item -q, --quiet
@opindex quiet
Try to be as quiet as possible.
@item --batch
@itemx --no-batch
@opindex batch
@opindex no-batch
Use batch mode. Never ask, do not allow interactive commands.
@option{--no-batch} disables this option. Note that even with a
filename given on the command line, gpg might still need to read from
STDIN (in particular if gpg figures that the input is a
detached signature and no data file has been specified). Thus if you
do not want to feed data via STDIN, you should connect STDIN to
g@file{/dev/null}.
It is highly recommended to use this option along with the options
@option{--status-fd} and @option{--with-colons} for any unattended use of
@command{gpg}.
@item --no-tty
@opindex no-tty
Make sure that the TTY (terminal) is never used for any output.
This option is needed in some cases because GnuPG sometimes prints
warnings to the TTY even if @option{--batch} is used.
@item --yes
@opindex yes
Assume "yes" on most questions.
@item --no
@opindex no
Assume "no" on most questions.
@item --list-options @var{parameters}
@opindex list-options
This is a space or comma delimited string that gives options used when
listing keys and signatures (that is, @option{--list-keys},
@option{--check-signatures}, @option{--list-public-keys},
@option{--list-secret-keys}, and the @option{--edit-key} functions).
Options can be prepended with a @option{no-} (after the two dashes) to
give the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex list-options:show-photos
Causes @option{--list-keys}, @option{--check-signatures},
@option{--list-public-keys}, and @option{--list-secret-keys} to
display any photo IDs attached to the key. Defaults to no. See also
@option{--photo-viewer}. Does not work with @option{--with-colons}:
see @option{--attribute-fd} for the appropriate way to get photo data
for scripts and other frontends.
@item show-usage
@opindex list-options:show-usage
Show usage information for keys and subkeys in the standard key
listing. This is a list of letters indicating the allowed usage for a
key (@code{E}=encryption, @code{S}=signing, @code{C}=certification,
@code{A}=authentication). Defaults to yes.
@item show-policy-urls
@opindex list-options:show-policy-urls
Show policy URLs in the @option{--check-signatures}
listings. Defaults to no.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex list-options:show-notations
@opindex list-options:show-std-notations
@opindex list-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
@option{--check-signatures} listings. Defaults to no.
@item show-keyserver-urls
@opindex list-options:show-keyserver-urls
Show any preferred keyserver URL in the
@option{--check-signatures} listings. Defaults to no.
@item show-uid-validity
@opindex list-options:show-uid-validity
Display the calculated validity of user IDs during key listings.
Defaults to yes.
@item show-unusable-uids
@opindex list-options:show-unusable-uids
Show revoked and expired user IDs in key listings. Defaults to no.
@item show-unusable-subkeys
@opindex list-options:show-unusable-subkeys
Show revoked and expired subkeys in key listings. Defaults to no.
@item show-keyring
@opindex list-options:show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. Defaults to no.
@item show-sig-expire
@opindex list-options:show-sig-expire
Show signature expiration dates (if any) during
@option{--check-signatures} listings. Defaults to no.
@item show-sig-subpackets
@opindex list-options:show-sig-subpackets
Include signature subpackets in the key listing. This option can take an
optional argument list of the subpackets to list. If no argument is
passed, list all subpackets. Defaults to no. This option is only
meaningful when using @option{--with-colons} along with
@option{--check-signatures}.
@item show-only-fpr-mbox
@opindex list-options:show-only-fpr-mbox
For each user-id which has a valid mail address print
only the fingerprint followed by the mail address.
@end table
@item --verify-options @var{parameters}
@opindex verify-options
This is a space or comma delimited string that gives options used when
verifying signatures. Options can be prepended with a `no-' to give
the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex verify-options:show-photos
Display any photo IDs present on the key that issued the signature.
Defaults to no. See also @option{--photo-viewer}.
@item show-policy-urls
@opindex verify-options:show-policy-urls
Show policy URLs in the signature being verified. Defaults to yes.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex verify-options:show-notations
@opindex verify-options:show-std-notations
@opindex verify-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
signature being verified. Defaults to IETF standard.
@item show-keyserver-urls
@opindex verify-options:show-keyserver-urls
Show any preferred keyserver URL in the signature being verified.
Defaults to yes.
@item show-uid-validity
@opindex verify-options:show-uid-validity
Display the calculated validity of the user IDs on the key that issued
the signature. Defaults to yes.
@item show-unusable-uids
@opindex verify-options:show-unusable-uids
Show revoked and expired user IDs during signature verification.
Defaults to no.
@item show-primary-uid-only
@opindex verify-options:show-primary-uid-only
Show only the primary user ID during signature verification. That is
all the AKA lines as well as photo Ids are not shown with the signature
verification status.
@item pka-lookups
@opindex verify-options:pka-lookups
Enable PKA lookups to verify sender addresses. Note that PKA is based
on DNS, and so enabling this option may disclose information on when
and what signatures are verified or to whom data is encrypted. This
is similar to the "web bug" described for the @option{--auto-key-retrieve}
option.
@item pka-trust-increase
@opindex verify-options:pka-trust-increase
Raise the trust in a signature to full if the signature passes PKA
validation. This option is only meaningful if pka-lookups is set.
@end table
@item --enable-large-rsa
@itemx --disable-large-rsa
@opindex enable-large-rsa
@opindex disable-large-rsa
With --generate-key and --batch, enable the creation of RSA secret keys as
large as 8192 bit. Note: 8192 bit is more than is generally
recommended. These large keys don't significantly improve security,
but they are more expensive to use, and their signatures and
certifications are larger. This option is only available if the
binary was build with large-secmem support.
@item --enable-dsa2
@itemx --disable-dsa2
@opindex enable-dsa2
@opindex disable-dsa2
Enable hash truncation for all DSA keys even for old DSA Keys up to
1024 bit. This is also the default with @option{--openpgp}. Note
that older versions of GnuPG also required this flag to allow the
generation of DSA larger than 1024 bit.
@item --photo-viewer @var{string}
@opindex photo-viewer
This is the command line that should be run to view a photo ID. "%i"
will be expanded to a filename containing the photo. "%I" does the
same, except the file will not be deleted once the viewer exits.
Other flags are "%k" for the key ID, "%K" for the long key ID, "%f"
for the key fingerprint, "%t" for the extension of the image type
(e.g. "jpg"), "%T" for the MIME type of the image (e.g. "image/jpeg"),
"%v" for the single-character calculated validity of the image being
viewed (e.g. "f"), "%V" for the calculated validity as a string (e.g.
"full"), "%U" for a base32 encoded hash of the user ID,
and "%%" for an actual percent sign. If neither %i or %I are present,
then the photo will be supplied to the viewer on standard input.
On Unix the default viewer is
@code{xloadimage -fork -quiet -title 'KeyID 0x%k' STDIN}
with a fallback to
@code{display -title 'KeyID 0x%k' %i}
and finally to
@code{xdg-open %i}.
On Windows
@code{!ShellExecute 400 %i} is used; here the command is a meta
command to use that API call followed by a wait time in milliseconds
which is used to give the viewer time to read the temporary image file
before gpg deletes it again. Note that if your image viewer program
is not secure, then executing it from gpg does not make it secure.
@item --exec-path @var{string}
@opindex exec-path
@efindex PATH
Sets a list of directories to search for photo viewers If not provided
photo viewers use the @code{PATH} environment variable.
@item --keyring @var{file}
@opindex keyring
Add @var{file} to the current list of keyrings. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory ("~/.gnupg" if @option{--homedir} or $GNUPGHOME is not
used).
Note that this adds a keyring to the current list. If the intent is to
use the specified keyring alone, use @option{--keyring} along with
@option{--no-default-keyring}.
If the option @option{--no-keyring} has been used no keyrings will
be used at all.
@item --secret-keyring @var{file}
@opindex secret-keyring
This is an obsolete option and ignored. All secret keys are stored in
the @file{private-keys-v1.d} directory below the GnuPG home directory.
@item --primary-keyring @var{file}
@opindex primary-keyring
Designate @var{file} as the primary public keyring. This means that
newly imported keys (via @option{--import} or keyserver
@option{--recv-from}) will go to this keyring.
@item --trustdb-name @var{file}
@opindex trustdb-name
Use @var{file} instead of the default trustdb. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory (@file{~/.gnupg} if @option{--homedir} or $GNUPGHOME is
not used).
@include opt-homedir.texi
@item --display-charset @var{name}
@opindex display-charset
Set the name of the native character set. This is used to convert
some informational strings like user IDs to the proper UTF-8 encoding.
Note that this has nothing to do with the character set of data to be
encrypted or signed; GnuPG does not recode user-supplied data. If
this option is not used, the default character set is determined from
the current locale. A verbosity level of 3 shows the chosen set.
Valid values for @var{name} are:
@table @asis
@item iso-8859-1
@opindex display-charset:iso-8859-1
This is the Latin 1 set.
@item iso-8859-2
@opindex display-charset:iso-8859-2
The Latin 2 set.
@item iso-8859-15
@opindex display-charset:iso-8859-15
This is currently an alias for
the Latin 1 set.
@item koi8-r
@opindex display-charset:koi8-r
The usual Russian set (RFC-1489).
@item utf-8
@opindex display-charset:utf-8
Bypass all translations and assume
that the OS uses native UTF-8 encoding.
@end table
@item --utf8-strings
@itemx --no-utf8-strings
@opindex utf8-strings
Assume that command line arguments are given as UTF-8 strings. The
default (@option{--no-utf8-strings}) is to assume that arguments are
encoded in the character set as specified by
@option{--display-charset}. These options affect all following
arguments. Both options may be used multiple times.
@anchor{gpg-option --options}
@item --options @var{file}
@opindex options
Read options from @var{file} and do not try to read them from the
default options file in the homedir (see @option{--homedir}). This
option is ignored if used in an options file.
@item --no-options
@opindex no-options
Shortcut for @option{--options /dev/null}. This option is detected
before an attempt to open an option file. Using this option will also
prevent the creation of a @file{~/.gnupg} homedir.
@item -z @var{n}
@itemx --compress-level @var{n}
@itemx --bzip2-compress-level @var{n}
@opindex compress-level
@opindex bzip2-compress-level
Set compression level to @var{n} for the ZIP and ZLIB compression
algorithms. The default is to use the default compression level of zlib
(normally 6). @option{--bzip2-compress-level} sets the compression level
for the BZIP2 compression algorithm (defaulting to 6 as well). This is a
different option from @option{--compress-level} since BZIP2 uses a
significant amount of memory for each additional compression level.
@option{-z} sets both. A value of 0 for @var{n} disables compression.
@item --bzip2-decompress-lowmem
@opindex bzip2-decompress-lowmem
Use a different decompression method for BZIP2 compressed files. This
alternate method uses a bit more than half the memory, but also runs
at half the speed. This is useful under extreme low memory
circumstances when the file was originally compressed at a high
@option{--bzip2-compress-level}.
@item --mangle-dos-filenames
@itemx --no-mangle-dos-filenames
@opindex mangle-dos-filenames
@opindex no-mangle-dos-filenames
Older version of Windows cannot handle filenames with more than one
dot. @option{--mangle-dos-filenames} causes GnuPG to replace (rather
than add to) the extension of an output filename to avoid this
problem. This option is off by default and has no effect on non-Windows
platforms.
@item --ask-cert-level
@itemx --no-ask-cert-level
@opindex ask-cert-level
When making a key signature, prompt for a certification level. If this
option is not specified, the certification level used is set via
@option{--default-cert-level}. See @option{--default-cert-level} for
information on the specific levels and how they are
used. @option{--no-ask-cert-level} disables this option. This option
defaults to no.
@item --default-cert-level @var{n}
@opindex default-cert-level
The default to use for the check level when signing a key.
0 means you make no particular claim as to how carefully you verified
the key.
1 means you believe the key is owned by the person who claims to own
it but you could not, or did not verify the key at all. This is
useful for a "persona" verification, where you sign the key of a
pseudonymous user.
2 means you did casual verification of the key. For example, this
could mean that you verified the key fingerprint and checked the
user ID on the key against a photo ID.
3 means you did extensive verification of the key. For example, this
could mean that you verified the key fingerprint with the owner of the
key in person, and that you checked, by means of a hard to forge
document with a photo ID (such as a passport) that the name of the key
owner matches the name in the user ID on the key, and finally that you
verified (by exchange of email) that the email address on the key
belongs to the key owner.
Note that the examples given above for levels 2 and 3 are just that:
examples. In the end, it is up to you to decide just what "casual"
and "extensive" mean to you.
This option defaults to 0 (no particular claim).
@item --min-cert-level
@opindex min-cert-level
When building the trust database, treat any signatures with a
certification level below this as invalid. Defaults to 2, which
disregards level 1 signatures. Note that level 0 "no particular
claim" signatures are always accepted.
@item --trusted-key @var{long key ID}
@opindex trusted-key
Assume that the specified key (which must be given
as a full 8 byte key ID) is as trustworthy as one of
your own secret keys. This option is useful if you
don't want to keep your secret keys (or one of them)
online but still want to be able to check the validity of a given
recipient's or signator's key.
@item --trust-model @{pgp|classic|tofu|tofu+pgp|direct|always|auto@}
@opindex trust-model
Set what trust model GnuPG should follow. The models are:
@table @asis
@item pgp
@opindex trust-model:pgp
This is the Web of Trust combined with trust signatures as used in PGP
5.x and later. This is the default trust model when creating a new
trust database.
@item classic
@opindex trust-model:classic
This is the standard Web of Trust as introduced by PGP 2.
@item tofu
@opindex trust-model:tofu
@anchor{trust-model-tofu}
TOFU stands for Trust On First Use. In this trust model, the first
time a key is seen, it is memorized. If later another key with a
user id with the same email address is seen, both keys are marked as
suspect. In that case, the next time either is used, a warning is
displayed describing the conflict, why it might have occurred
(either the user generated a new key and failed to cross sign the
old and new keys, the key is forgery, or a man-in-the-middle attack
is being attempted), and the user is prompted to manually confirm
the validity of the key in question.
Because a potential attacker is able to control the email address
and thereby circumvent the conflict detection algorithm by using an
email address that is similar in appearance to a trusted email
address, whenever a message is verified, statistics about the number
of messages signed with the key are shown. In this way, a user can
easily identify attacks using fake keys for regular correspondents.
When compared with the Web of Trust, TOFU offers significantly
weaker security guarantees. In particular, TOFU only helps ensure
consistency (that is, that the binding between a key and email
address doesn't change). A major advantage of TOFU is that it
requires little maintenance to use correctly. To use the web of
trust properly, you need to actively sign keys and mark users as
trusted introducers. This is a time-consuming process and anecdotal
evidence suggests that even security-conscious users rarely take the
time to do this thoroughly and instead rely on an ad-hoc TOFU
process.
In the TOFU model, policies are associated with bindings between
keys and email addresses (which are extracted from user ids and
normalized). There are five policies, which can be set manually
using the @option{--tofu-policy} option. The default policy can be
set using the @option{--tofu-default-policy} option.
The TOFU policies are: @code{auto}, @code{good}, @code{unknown},
@code{bad} and @code{ask}. The @code{auto} policy is used by
default (unless overridden by @option{--tofu-default-policy}) and
marks a binding as marginally trusted. The @code{good},
@code{unknown} and @code{bad} policies mark a binding as fully
trusted, as having unknown trust or as having trust never,
respectively. The @code{unknown} policy is useful for just using
TOFU to detect conflicts, but to never assign positive trust to a
binding. The final policy, @code{ask} prompts the user to indicate
the binding's trust. If batch mode is enabled (or input is
inappropriate in the context), then the user is not prompted and the
@code{undefined} trust level is returned.
@item tofu+pgp
@opindex trust-model:tofu+pgp
This trust model combines TOFU with the Web of Trust. This is done
by computing the trust level for each model and then taking the
maximum trust level where the trust levels are ordered as follows:
@code{unknown < undefined < marginal < fully < ultimate < expired <
never}.
By setting @option{--tofu-default-policy=unknown}, this model can be
used to implement the web of trust with TOFU's conflict detection
algorithm, but without its assignment of positive trust values,
which some security-conscious users don't like.
@item direct
@opindex trust-model:direct
Key validity is set directly by the user and not calculated via the
Web of Trust. This model is solely based on the key and does
not distinguish user IDs. Note that when changing to another trust
model the trust values assigned to a key are transformed into
ownertrust values, which also indicate how you trust the owner of
the key to sign other keys.
@item always
@opindex trust-model:always
Skip key validation and assume that used keys are always fully
valid. You generally won't use this unless you are using some
external validation scheme. This option also suppresses the
"[uncertain]" tag printed with signature checks when there is no
evidence that the user ID is bound to the key. Note that this
trust model still does not allow the use of expired, revoked, or
disabled keys.
@item auto
@opindex trust-model:auto
Select the trust model depending on whatever the internal trust
database says. This is the default model if such a database already
exists. Note that a tofu trust model is not considered here and
must be enabled explicitly.
@end table
@item --auto-key-locate @var{mechanisms}
@itemx --no-auto-key-locate
@opindex auto-key-locate
GnuPG can automatically locate and retrieve keys as needed using this
option. This happens when encrypting to an email address (in the
"user@@example.com" form), and there are no "user@@example.com" keys
on the local keyring. This option takes any number of the mechanisms
listed below, in the order they are to be tried. Instead of listing
the mechanisms as comma delimited arguments, the option may also be
given several times to add more mechanism. The option
@option{--no-auto-key-locate} or the mechanism "clear" resets the
list. The default is "local,wkd".
@table @asis
@item cert
Locate a key using DNS CERT, as specified in RFC-4398.
@item pka
Locate a key using DNS PKA.
@item dane
Locate a key using DANE, as specified
in draft-ietf-dane-openpgpkey-05.txt.
@item wkd
Locate a key using the Web Key Directory protocol.
@item ldap
Using DNS Service Discovery, check the domain in question for any LDAP
keyservers to use. If this fails, attempt to locate the key using the
PGP Universal method of checking @samp{ldap://keys.(thedomain)}.
@item keyserver
Locate a key using a keyserver.
@item keyserver-URL
In addition, a keyserver URL as used in the @command{dirmngr}
configuration may be used here to query that particular keyserver.
@item local
Locate the key using the local keyrings. This mechanism allows the user to
select the order a local key lookup is done. Thus using
@samp{--auto-key-locate local} is identical to
@option{--no-auto-key-locate}.
@item nodefault
This flag disables the standard local key lookup, done before any of the
mechanisms defined by the @option{--auto-key-locate} are tried. The
position of this mechanism in the list does not matter. It is not
required if @code{local} is also used.
@item clear
Clear all defined mechanisms. This is useful to override
mechanisms given in a config file. Note that a @code{nodefault} in
@var{mechanisms} will also be cleared unless it is given after the
@code{clear}.
@end table
@item --auto-key-retrieve
@itemx --no-auto-key-retrieve
@opindex auto-key-retrieve
@opindex no-auto-key-retrieve
These options enable or disable the automatic retrieving of keys from
a keyserver when verifying signatures made by keys that are not on the
local keyring. The default is @option{--no-auto-key-retrieve}.
The order of methods tried to lookup the key is:
1. If a preferred keyserver is specified in the signature and the
option @option{honor-keyserver-url} is active (which is not the
default), that keyserver is tried. Note that the creator of the
signature uses the option @option{--sig-keyserver-url} to specify the
preferred keyserver for data signatures.
2. If the signature has the Signer's UID set (e.g. using
@option{--sender} while creating the signature) a Web Key Directory
(WKD) lookup is done. This is the default configuration but can be
disabled by removing WKD from the auto-key-locate list or by using the
option @option{--disable-signer-uid}.
3. If the option @option{honor-pka-record} is active, the legacy PKA
method is used.
4. If any keyserver is configured and the Issuer Fingerprint is part
of the signature (since GnuPG 2.1.16), the configured keyservers are
tried.
Note that this option makes a "web bug" like behavior possible.
Keyserver or Web Key Directory operators can see which keys you
request, so by sending you a message signed by a brand new key (which
you naturally will not have on your local keyring), the operator can
tell both your IP address and the time when you verified the
signature.
@item --keyid-format @{none|short|0xshort|long|0xlong@}
@opindex keyid-format
Select how to display key IDs. "none" does not show the key ID at all
but shows the fingerprint in a separate line. "short" is the
traditional 8-character key ID. "long" is the more accurate (but less
convenient) 16-character key ID. Add an "0x" to either to include an
"0x" at the beginning of the key ID, as in 0x99242560. Note that this
option is ignored if the option @option{--with-colons} is used.
@item --keyserver @var{name}
@opindex keyserver
This option is deprecated - please use the @option{--keyserver} in
@file{dirmngr.conf} instead.
Use @var{name} as your keyserver. This is the server that
@option{--receive-keys}, @option{--send-keys}, and @option{--search-keys}
will communicate with to receive keys from, send keys to, and search for
keys on. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp" for the HTTP (or compatible) keyservers, "ldap" for the LDAP
keyservers, or "mailto" for the Graff email keyserver. Note that your
particular installation of GnuPG may have other keyserver types
available as well. Keyserver schemes are case-insensitive. After the
keyserver name, optional keyserver configuration options may be
provided. These are the same as the global @option{--keyserver-options}
from below, but apply only to this particular keyserver.
Most keyservers synchronize with each other, so there is generally no
need to send keys to more than one server. The keyserver
@code{hkp://keys.gnupg.net} uses round robin DNS to give a different
keyserver each time you use it.
@item --keyserver-options @{@var{name}=@var{value}@}
@opindex keyserver-options
This is a space or comma delimited string that gives options for the
keyserver. Options can be prefixed with a `no-' to give the opposite
meaning. Valid import-options or export-options may be used here as
well to apply to importing (@option{--recv-key}) or exporting
(@option{--send-key}) a key from a keyserver. While not all options
are available for all keyserver types, some common options are:
@table @asis
@item include-revoked
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as revoked. Note that not all keyservers
differentiate between revoked and unrevoked keys, and for such
keyservers this option is meaningless. Note also that most keyservers do
not have cryptographic verification of key revocations, and so turning
this option off may result in skipping keys that are incorrectly marked
as revoked.
@item include-disabled
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as disabled. Note that this option is not
used with HKP keyservers.
@item auto-key-retrieve
This is an obsolete alias for the option @option{auto-key-retrieve}.
Please do not use it; it will be removed in future versions..
@item honor-keyserver-url
When using @option{--refresh-keys}, if the key in question has a preferred
keyserver URL, then use that preferred keyserver to refresh the key
from. In addition, if auto-key-retrieve is set, and the signature
being verified has a preferred keyserver URL, then use that preferred
keyserver to fetch the key from. Note that this option introduces a
"web bug": The creator of the key can see when the keys is
refreshed. Thus this option is not enabled by default.
@item honor-pka-record
If @option{--auto-key-retrieve} is used, and the signature being
verified has a PKA record, then use the PKA information to fetch
the key. Defaults to "yes".
@item include-subkeys
When receiving a key, include subkeys as potential targets. Note that
this option is not used with HKP keyservers, as they do not support
retrieving keys by subkey id.
@item timeout
@itemx http-proxy=@var{value}
@itemx verbose
@itemx debug
@itemx check-cert
@item ca-cert-file
These options have no more function since GnuPG 2.1. Use the
@code{dirmngr} configuration options instead.
@end table
The default list of options is: "self-sigs-only, import-clean,
repair-keys, repair-pks-subkey-bug, export-attributes,
honor-pka-record".
@item --completes-needed @var{n}
@opindex compliant-needed
Number of completely trusted users to introduce a new
key signer (defaults to 1).
@item --marginals-needed @var{n}
@opindex marginals-needed
Number of marginally trusted users to introduce a new
key signer (defaults to 3)
@item --tofu-default-policy @{auto|good|unknown|bad|ask@}
@opindex tofu-default-policy
The default TOFU policy (defaults to @code{auto}). For more
information about the meaning of this option, @pxref{trust-model-tofu}.
@item --max-cert-depth @var{n}
@opindex max-cert-depth
Maximum depth of a certification chain (default is 5).
@item --no-sig-cache
@opindex no-sig-cache
Do not cache the verification status of key signatures.
Caching gives a much better performance in key listings. However, if
you suspect that your public keyring is not safe against write
modifications, you can use this option to disable the caching. It
probably does not make sense to disable it because all kind of damage
can be done if someone else has write access to your public keyring.
@item --auto-check-trustdb
@itemx --no-auto-check-trustdb
@opindex auto-check-trustdb
If GnuPG feels that its information about the Web of Trust has to be
updated, it automatically runs the @option{--check-trustdb} command
internally. This may be a time consuming
process. @option{--no-auto-check-trustdb} disables this option.
@item --use-agent
@itemx --no-use-agent
@opindex use-agent
This is dummy option. @command{@gpgname} always requires the agent.
@item --gpg-agent-info
@opindex gpg-agent-info
This is dummy option. It has no effect when used with @command{@gpgname}.
@item --agent-program @var{file}
@opindex agent-program
Specify an agent program to be used for secret key operations. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify a dirmngr program to be used for keyserver access. The
default value is @file{@value{BINDIR}/dirmngr}.
@item --disable-dirmngr
Entirely disable the use of the Dirmngr.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started and its service is required. This option is mostly useful on
machines where the connection to gpg-agent has been redirected to
another machines. If dirmngr is required on the remote machine, it
may be started manually using @command{gpgconf --launch dirmngr}.
@item --lock-once
@opindex lock-once
Lock the databases the first time a lock is requested
and do not release the lock until the process
terminates.
@item --lock-multiple
@opindex lock-multiple
Release the locks every time a lock is no longer
needed. Use this to override a previous @option{--lock-once}
from a config file.
@item --lock-never
@opindex lock-never
Disable locking entirely. This option should be used only in very
special environments, where it can be assured that only one process
is accessing those files. A bootable floppy with a stand-alone
encryption system will probably use this. Improper usage of this
option may lead to data and key corruption.
@item --exit-on-status-write-error
@opindex exit-on-status-write-error
This option will cause write errors on the status FD to immediately
terminate the process. That should in fact be the default but it never
worked this way and thus we need an option to enable this, so that the
change won't break applications which close their end of a status fd
connected pipe too early. Using this option along with
@option{--enable-progress-filter} may be used to cleanly cancel long
running gpg operations.
@item --limit-card-insert-tries @var{n}
@opindex limit-card-insert-tries
With @var{n} greater than 0 the number of prompts asking to insert a
smartcard gets limited to N-1. Thus with a value of 1 gpg won't at
all ask to insert a card if none has been inserted at startup. This
option is useful in the configuration file in case an application does
not know about the smartcard support and waits ad infinitum for an
inserted card.
@item --no-random-seed-file
@opindex no-random-seed-file
GnuPG uses a file to store its internal random pool over invocations.
This makes random generation faster; however sometimes write operations
are not desired. This option can be used to achieve that with the cost of
slower random generation.
@item --no-greeting
@opindex no-greeting
Suppress the initial copyright message.
@item --no-secmem-warning
@opindex no-secmem-warning
Suppress the warning about "using insecure memory".
@item --no-permission-warning
@opindex permission-warning
Suppress the warning about unsafe file and home directory (@option{--homedir})
permissions. Note that the permission checks that GnuPG performs are
not intended to be authoritative, but rather they simply warn about
certain common permission problems. Do not assume that the lack of a
warning means that your system is secure.
Note that the warning for unsafe @option{--homedir} permissions cannot be
suppressed in the gpg.conf file, as this would allow an attacker to
place an unsafe gpg.conf file in place, and use this file to suppress
warnings about itself. The @option{--homedir} permissions warning may only be
suppressed on the command line.
@item --require-secmem
@itemx --no-require-secmem
@opindex require-secmem
Refuse to run if GnuPG cannot get secure memory. Defaults to no
(i.e. run, but give a warning).
@item --require-cross-certification
@itemx --no-require-cross-certification
@opindex require-cross-certification
When verifying a signature made from a subkey, ensure that the cross
certification "back signature" on the subkey is present and valid. This
protects against a subtle attack against subkeys that can sign.
Defaults to @option{--require-cross-certification} for
@command{@gpgname}.
@item --expert
@itemx --no-expert
@opindex expert
Allow the user to do certain nonsensical or "silly" things like
signing an expired or revoked key, or certain potentially incompatible
things like generating unusual key types. This also disables certain
warning messages about potentially incompatible actions. As the name
implies, this option is for experts only. If you don't fully
understand the implications of what it allows you to do, leave this
off. @option{--no-expert} disables this option.
@end table
@c *******************************************
@c ******** KEY RELATED OPTIONS ************
@c *******************************************
@node GPG Key related Options
@subsection Key related options
@table @gnupgtabopt
@item --recipient @var{name}
@itemx -r
@opindex recipient
Encrypt for user id @var{name}. If this option or
@option{--hidden-recipient} is not specified, GnuPG asks for the user-id
unless @option{--default-recipient} is given.
@item --hidden-recipient @var{name}
@itemx -R
@opindex hidden-recipient
Encrypt for user ID @var{name}, but hide the key ID of this user's
key. This option helps to hide the receiver of the message and is a
limited countermeasure against traffic analysis. If this option or
@option{--recipient} is not specified, GnuPG asks for the user ID unless
@option{--default-recipient} is given.
@item --recipient-file @var{file}
@itemx -f
@opindex recipient-file
This option is similar to @option{--recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --hidden-recipient-file @var{file}
@itemx -F
@opindex hidden-recipient-file
This option is similar to @option{--hidden-recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --encrypt-to @var{name}
@opindex encrypt-to
Same as @option{--recipient} but this one is intended for use in the
options file and may be used with your own user-id as an
"encrypt-to-self". These keys are only used when there are other
recipients given either by use of @option{--recipient} or by the asked
user id. No trust checking is performed for these user ids and even
disabled keys can be used.
@item --hidden-encrypt-to @var{name}
@opindex hidden-encrypt-to
Same as @option{--hidden-recipient} but this one is intended for use in the
options file and may be used with your own user-id as a hidden
"encrypt-to-self". These keys are only used when there are other
recipients given either by use of @option{--recipient} or by the asked user id.
No trust checking is performed for these user ids and even disabled
keys can be used.
@item --no-encrypt-to
@opindex no-encrypt-to
Disable the use of all @option{--encrypt-to} and
@option{--hidden-encrypt-to} keys.
@item --group @{@var{name}=@var{value}@}
@opindex group
Sets up a named group, which is similar to aliases in email programs.
Any time the group name is a recipient (@option{-r} or
@option{--recipient}), it will be expanded to the values
specified. Multiple groups with the same name are automatically merged
into a single group.
The values are @code{key IDs} or fingerprints, but any key description
is accepted. Note that a value with spaces in it will be treated as
two different values. Note also there is only one level of expansion
--- you cannot make an group that points to another group. When used
from the command line, it may be necessary to quote the argument to
this option to prevent the shell from treating it as multiple
arguments.
@item --ungroup @var{name}
@opindex ungroup
Remove a given entry from the @option{--group} list.
@item --no-groups
@opindex no-groups
Remove all entries from the @option{--group} list.
@item --local-user @var{name}
@itemx -u
@opindex local-user
Use @var{name} as the key to sign with. Note that this option overrides
@option{--default-key}.
@item --sender @var{mbox}
@opindex sender
This option has two purposes. @var{mbox} must either be a complete
user id with a proper mail address or just a mail address. When
creating a signature this option tells gpg the user id of a key used
to make a signature if the key was not directly specified by a user
id. When verifying a signature the @var{mbox} is used to restrict the
information printed by the TOFU code to matching user ids.
@item --try-secret-key @var{name}
@opindex try-secret-key
For hidden recipients GPG needs to know the keys to use for trial
decryption. The key set with @option{--default-key} is always tried
first, but this is often not sufficient. This option allows setting more
keys to be used for trial decryption. Although any valid user-id
specification may be used for @var{name} it makes sense to use at least
the long keyid to avoid ambiguities. Note that gpg-agent might pop up a
pinentry for a lot keys to do the trial decryption. If you want to stop
all further trial decryption you may use close-window button instead of
the cancel button.
@item --try-all-secrets
@opindex try-all-secrets
Don't look at the key ID as stored in the message but try all secret
keys in turn to find the right decryption key. This option forces the
behaviour as used by anonymous recipients (created by using
@option{--throw-keyids} or @option{--hidden-recipient}) and might come
handy in case where an encrypted message contains a bogus key ID.
@item --skip-hidden-recipients
@itemx --no-skip-hidden-recipients
@opindex skip-hidden-recipients
@opindex no-skip-hidden-recipients
During decryption skip all anonymous recipients. This option helps in
the case that people use the hidden recipients feature to hide their
own encrypt-to key from others. If one has many secret keys this
may lead to a major annoyance because all keys are tried in turn to
decrypt something which was not really intended for it. The drawback
of this option is that it is currently not possible to decrypt a
message which includes real anonymous recipients.
@end table
@c *******************************************
@c ******** INPUT AND OUTPUT ***************
@c *******************************************
@node GPG Input and Output
@subsection Input and Output
@table @gnupgtabopt
@item --armor
@itemx -a
@opindex armor
Create ASCII armored output. The default is to create the binary
OpenPGP format.
@item --no-armor
@opindex no-armor
Assume the input data is not in ASCII armored format.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write output to @var{file}. To write to stdout use @code{-} as the
filename.
@item --max-output @var{n}
@opindex max-output
This option sets a limit on the number of bytes that will be generated
when processing a file. Since OpenPGP supports various levels of
compression, it is possible that the plaintext of a given message may be
significantly larger than the original OpenPGP message. While GnuPG
works properly with such messages, there is often a desire to set a
maximum file size that will be generated before processing is forced to
stop by the OS limits. Defaults to 0, which means "no limit".
@item --chunk-size @var{n}
@opindex chunk-size
The AEAD encryption mode encrypts the data in chunks so that a
receiving side can check for transmission errors or tampering at the
end of each chunk and does not need to delay this until all data has
been received. The used chunk size is 2^@var{n} byte. The lowest
allowed value for @var{n} is 6 (64 byte) and the largest is the
default of 27 which creates chunks not larger than 128 MiB.
@item --input-size-hint @var{n}
@opindex input-size-hint
This option can be used to tell GPG the size of the input data in
bytes. @var{n} must be a positive base-10 number. This option is
only useful if the input is not taken from a file. GPG may use this
hint to optimize its buffer allocation strategy. It is also used by
the @option{--status-fd} line ``PROGRESS'' to provide a value for
``total'' if that is not available by other means.
@item --key-origin @var{string}[,@var{url}]
@opindex key-origin
gpg can track the origin of a key. Certain origins are implicitly
known (e.g. keyserver, web key directory) and set. For a standard
import the origin of the keys imported can be set with this option.
To list the possible values use "help" for @var{string}. Some origins
can store an optional @var{url} argument. That URL can appended to
@var{string} after a comma.
@item --import-options @var{parameters}
@opindex import-options
This is a space or comma delimited string that gives options for
importing keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item import-local-sigs
Allow importing key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item keep-ownertrust
Normally possible still existing ownertrust values of a key are
cleared if a key is imported. This is in general desirable so that
a formerly deleted key does not automatically gain an ownertrust
values merely due to import. On the other hand it is sometimes
necessary to re-import a trusted set of keys again but keeping
already assigned ownertrust values. This can be achieved by using
this option.
@item repair-pks-subkey-bug
During import, attempt to repair the damage caused by the PKS keyserver
bug (pre version 0.9.6) that mangles keys with multiple subkeys. Note
that this cannot completely repair the damaged key as some crucial data
is removed by the keyserver, but it does at least give you back one
subkey. Defaults to no for regular @option{--import} and to yes for
keyserver @option{--receive-keys}.
@item import-show
@itemx show-only
Show a listing of the key as imported right before it is stored.
This can be combined with the option @option{--dry-run} to only look
at keys; the option @option{show-only} is a shortcut for this
combination. The command @option{--show-keys} is another shortcut
for this. Note that suffixes like '#' for "sec" and "sbb" lines
may or may not be printed.
@item import-export
Run the entire import code but instead of storing the key to the
local keyring write it to the output. The export options
@option{export-pka} and @option{export-dane} affect the output. This
option can be used to remove all invalid parts from a key without the
need to store it.
@item merge-only
During import, allow key updates to existing keys, but do not allow
any new keys to be imported. Defaults to no.
@item import-clean
After import, compact (remove all signatures except the
self-signature) any user IDs from the new key that are not usable.
Then, remove any signatures from the new key that are not usable.
This includes signatures that were issued by keys that are not present
on the keyring. This option is the same as running the @option{--edit-key}
command "clean" after import. Defaults to no.
@item import-drop-uids
Do not import any user ids or their binding signatures. This option
can be used to update only the subkeys or other non-user id related
information.
@item self-sigs-only
Accept only self-signatures while importing a key. All other key
signatures are skipped at an early import stage. This option can be
used with @code{keyserver-options} to mitigate attempts to flood a
key with bogus signatures from a keyserver. The drawback is that
all other valid key signatures, as required by the Web of Trust are
also not imported. Note that when using this option along with
import-clean it suppresses the final clean step after merging the
imported key into the existing key.
@item repair-keys
After import, fix various problems with the
keys. For example, this reorders signatures, and strips duplicate
signatures. Defaults to yes.
@item import-minimal
Import the smallest key possible. This removes all signatures except
the most recent self-signature on each user ID. This option is the
same as running the @option{--edit-key} command "minimize" after import.
Defaults to no.
@item restore
@itemx import-restore
Import in key restore mode. This imports all data which is usually
skipped during import; including all GnuPG specific data. All other
contradicting options are overridden.
@end table
@item --import-filter @{@var{name}=@var{expr}@}
@itemx --export-filter @{@var{name}=@var{expr}@}
@opindex import-filter
@opindex export-filter
These options define an import/export filter which are applied to the
imported/exported keyblock right before it will be stored/written.
@var{name} defines the type of filter to use, @var{expr} the
expression to evaluate. The option can be used several times which
then appends more expression to the same @var{name}.
@noindent
The available filter types are:
@table @asis
@item keep-uid
This filter will keep a user id packet and its dependent packets in
the keyblock if the expression evaluates to true.
@item drop-subkey
This filter drops the selected subkeys.
Currently only implemented for --export-filter.
@item drop-sig
This filter drops the selected key signatures on user ids.
Self-signatures are not considered.
Currently only implemented for --import-filter.
@end table
For the syntax of the expression see the chapter "FILTER EXPRESSIONS".
The property names for the expressions depend on the actual filter
type and are indicated in the following table.
The available properties are:
@table @asis
@item uid
A string with the user id. (keep-uid)
@item mbox
The addr-spec part of a user id with mailbox or the empty string.
(keep-uid)
@item key_algo
A number with the public key algorithm of a key or subkey packet.
(drop-subkey)
@item key_created
@itemx key_created_d
The first is the timestamp a public key or subkey packet was
created. The second is the same but given as an ISO string,
e.g. "2016-08-17". (drop-subkey)
@item primary
Boolean indicating whether the user id is the primary one. (keep-uid)
@item expired
Boolean indicating whether a user id (keep-uid), a key (drop-subkey), or a
signature (drop-sig) expired.
@item revoked
Boolean indicating whether a user id (keep-uid) or a key (drop-subkey) has
been revoked.
@item disabled
Boolean indicating whether a primary key is disabled. (not used)
@item secret
Boolean indicating whether a key or subkey is a secret one.
(drop-subkey)
@item usage
A string indicating the usage flags for the subkey, from the
sequence ``ecsa?''. For example, a subkey capable of just signing
and authentication would be an exact match for ``sa''. (drop-subkey)
@item sig_created
@itemx sig_created_d
The first is the timestamp a signature packet was created. The
second is the same but given as an ISO date string,
e.g. "2016-08-17". (drop-sig)
@item sig_algo
A number with the public key algorithm of a signature packet. (drop-sig)
@item sig_digest_algo
A number with the digest algorithm of a signature packet. (drop-sig)
@end table
@item --export-options @var{parameters}
@opindex export-options
This is a space or comma delimited string that gives options for
exporting keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item export-local-sigs
Allow exporting key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item export-attributes
Include attribute user IDs (photo IDs) while exporting. Not
including attribute user IDs is useful to export keys that are going
to be used by an OpenPGP program that does not accept attribute user
IDs. Defaults to yes.
@item export-sensitive-revkeys
Include designated revoker information that was marked as
"sensitive". Defaults to no.
@c Since GnuPG 2.1 gpg-agent manages the secret key and thus the
@c export-reset-subkey-passwd hack is not anymore justified. Such use
@c cases may be implemented using a specialized secret key export
@c tool.
@c @item export-reset-subkey-passwd
@c When using the @option{--export-secret-subkeys} command, this option resets
@c the passphrases for all exported subkeys to empty. This is useful
@c when the exported subkey is to be used on an unattended machine where
@c a passphrase doesn't necessarily make sense. Defaults to no.
@item backup
@itemx export-backup
Export for use as a backup. The exported data includes all data
which is needed to restore the key or keys later with GnuPG. The
format is basically the OpenPGP format but enhanced with GnuPG
specific data. All other contradicting options are overridden.
@item export-clean
Compact (remove all signatures from) user IDs on the key being
exported if the user IDs are not usable. Also, do not export any
signatures that are not usable. This includes signatures that were
issued by keys that are not present on the keyring. This option is
the same as running the @option{--edit-key} command "clean" before export
except that the local copy of the key is not modified. Defaults to
no.
@item export-minimal
Export the smallest key possible. This removes all signatures except the
most recent self-signature on each user ID. This option is the same as
running the @option{--edit-key} command "minimize" before export except
that the local copy of the key is not modified. Defaults to no.
@item export-drop-uids
Do no export any user id or attribute packets or their associates
signatures. Note that due to missing user ids the resulting output is
not strictly RFC-4880 compliant.
@item export-pka
Instead of outputting the key material output PKA records suitable
to put into DNS zone files. An ORIGIN line is printed before each
record to allow diverting the records to the corresponding zone file.
@item export-dane
Instead of outputting the key material output OpenPGP DANE records
suitable to put into DNS zone files. An ORIGIN line is printed before
each record to allow diverting the records to the corresponding zone
file.
@end table
@item --with-colons
@opindex with-colons
Print key listings delimited by colons. Note that the output will be
encoded in UTF-8 regardless of any @option{--display-charset} setting. This
format is useful when GnuPG is called from scripts and other programs
as it is easily machine parsed. The details of this format are
documented in the file @file{doc/DETAILS}, which is included in the GnuPG
source distribution.
@item --fixed-list-mode
@opindex fixed-list-mode
Do not merge primary user ID and primary key in @option{--with-colon}
listing mode and print all timestamps as seconds since 1970-01-01.
Since GnuPG 2.0.10, this mode is always used and thus this option is
obsolete; it does not harm to use it though.
@item --legacy-list-mode
@opindex legacy-list-mode
Revert to the pre-2.1 public key list mode. This only affects the
human readable output and not the machine interface
(i.e. @code{--with-colons}). Note that the legacy format does not
convey suitable information for elliptic curves.
@item --with-fingerprint
@opindex with-fingerprint
Same as the command @option{--fingerprint} but changes only the format
of the output and may be used together with another command.
@item --with-subkey-fingerprint
@opindex with-subkey-fingerprint
If a fingerprint is printed for the primary key, this option forces
printing of the fingerprint for all subkeys. This could also be
achieved by using the @option{--with-fingerprint} twice but by using
this option along with keyid-format "none" a compact fingerprint is
printed.
@item --with-icao-spelling
@opindex with-icao-spelling
Print the ICAO spelling of the fingerprint in addition to the hex digits.
@item --with-keygrip
@opindex with-keygrip
Include the keygrip in the key listings. In @code{--with-colons} mode
this is implicitly enable for secret keys.
@item --with-key-origin
@opindex with-key-origin
Include the locally held information on the origin and last update of
a key in a key listing. In @code{--with-colons} mode this is always
printed. This data is currently experimental and shall not be
considered part of the stable API.
@item --with-wkd-hash
@opindex with-wkd-hash
Print a Web Key Directory identifier along with each user ID in key
listings. This is an experimental feature and semantics may change.
@item --with-secret
@opindex with-secret
Include info about the presence of a secret key in public key listings
done with @code{--with-colons}.
@end table
@c *******************************************
@c ******** OPENPGP OPTIONS ****************
@c *******************************************
@node OpenPGP Options
@subsection OpenPGP protocol specific options
@table @gnupgtabopt
@item -t, --textmode
@itemx --no-textmode
@opindex textmode
Treat input files as text and store them in the OpenPGP canonical text
form with standard "CRLF" line endings. This also sets the necessary
flags to inform the recipient that the encrypted or signed data is text
and may need its line endings converted back to whatever the local
system uses. This option is useful when communicating between two
platforms that have different line ending conventions (UNIX-like to Mac,
Mac to Windows, etc). @option{--no-textmode} disables this option, and
is the default.
@item --force-v3-sigs
@itemx --no-force-v3-sigs
@item --force-v4-certs
@itemx --no-force-v4-certs
These options are obsolete and have no effect since GnuPG 2.1.
@item --force-aead
@opindex force-aead
Force the use of AEAD encryption over MDC encryption. AEAD is a
modern and faster way to do authenticated encryption than the old MDC
method. See also options @option{--aead-algo} and
@option{--chunk-size}.
@item --force-mdc
@itemx --disable-mdc
@opindex force-mdc
@opindex disable-mdc
These options are obsolete and have no effect since GnuPG 2.2.8. The
MDC is always used unless the keys indicate that an AEAD algorithm can
be used in which case AEAD is used. But note: If the creation of a
legacy non-MDC message is exceptionally required, the option
@option{--rfc2440} allows for this.
@item --disable-signer-uid
@opindex disable-signer-uid
By default the user ID of the signing key is embedded in the data signature.
As of now this is only done if the signing key has been specified with
@option{local-user} using a mail address, or with @option{sender}. This
information can be helpful for verifier to locate the key; see option
@option{--auto-key-retrieve}.
@item --personal-cipher-preferences @var{string}
@opindex personal-cipher-preferences
Set the list of personal cipher preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked cipher in this list is also
used for the @option{--symmetric} encryption command.
@item --personal-aead-preferences @var{string}
@opindex personal-aead-preferences
Set the list of personal AEAD preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked cipher in this list is also
used for the @option{--symmetric} encryption command.
@item --personal-digest-preferences @var{string}
@opindex personal-digest-preferences
Set the list of personal digest preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked digest algorithm in this list
is also used when signing without encryption
(e.g. @option{--clear-sign} or @option{--sign}).
@item --personal-compress-preferences @var{string}
@opindex personal-compress-preferences
Set the list of personal compression preferences to @var{string}.
Use @command{@gpgname --version} to get a list of available
algorithms, and use @code{none} to set no preference at all. This
allows the user to safely override the algorithm chosen by the
recipient key preferences, as GPG will only select an algorithm that
is usable by all recipients. The most highly ranked compression
algorithm in this list is also used when there are no recipient keys
to consider (e.g. @option{--symmetric}).
@item --s2k-cipher-algo @var{name}
@opindex s2k-cipher-algo
Use @var{name} as the cipher algorithm for symmetric encryption with
a passphrase if @option{--personal-cipher-preferences} and
@option{--cipher-algo} are not given. The default is @value{GPGSYMENCALGO}.
@item --s2k-digest-algo @var{name}
@opindex s2k-digest-algo
Use @var{name} as the digest algorithm used to mangle the passphrases
for symmetric encryption. The default is SHA-1.
@item --s2k-mode @var{n}
@opindex s2k-mode
Selects how passphrases for symmetric encryption are mangled. If
@var{n} is 0 a plain passphrase (which is in general not recommended)
will be used, a 1 adds a salt (which should not be used) to the
passphrase and a 3 (the default) iterates the whole process a number
of times (see @option{--s2k-count}).
@item --s2k-count @var{n}
@opindex s2k-count
Specify how many times the passphrases mangling for symmetric
encryption is repeated. This value may range between 1024 and
65011712 inclusive. The default is inquired from gpg-agent. Note
that not all values in the 1024-65011712 range are legal and if an
illegal value is selected, GnuPG will round up to the nearest legal
value. This option is only meaningful if @option{--s2k-mode} is set
to the default of 3.
@end table
@c ***************************
@c ******* Compliance ********
@c ***************************
@node Compliance Options
@subsection Compliance options
These options control what GnuPG is compliant to. Only one of these
options may be active at a time. Note that the default setting of
this is nearly always the correct one. See the INTEROPERABILITY WITH
OTHER OPENPGP PROGRAMS section below before using one of these
options.
@table @gnupgtabopt
@item --gnupg
@opindex gnupg
Use standard GnuPG behavior. This is essentially OpenPGP behavior (see
@option{--openpgp}), but with extension from the proposed update to
OpenPGP and with some additional workarounds for common compatibility
problems in different versions of PGP. This is the default option, so
it is not generally needed, but it may be useful to override a
different compliance option in the gpg.conf file.
@item --openpgp
@opindex openpgp
Reset all packet, cipher and digest options to strict OpenPGP
behavior. Use this option to reset all previous options like
@option{--s2k-*}, @option{--cipher-algo}, @option{--digest-algo} and
@option{--compress-algo} to OpenPGP compliant values. All PGP
workarounds are disabled.
@item --rfc4880
@opindex rfc4880
Reset all packet, cipher and digest options to strict RFC-4880
behavior. Note that this is currently the same thing as
@option{--openpgp}.
@item --rfc4880bis
@opindex rfc4880bis
Reset all packet, cipher and digest options to strict according to the
proposed updates of RFC-4880.
@item --rfc2440
@opindex rfc2440
Reset all packet, cipher and digest options to strict RFC-2440
behavior. Note that by using this option encryption packets are
created in a legacy mode without MDC protection. This is dangerous
and should thus only be used for experiments. See also option
@option{--ignore-mdc-error}.
@item --pgp6
@opindex pgp6
This option is obsolete; it is handled as an alias for @option{--pgp7}
@item --pgp7
@opindex pgp7
Set up all options to be as PGP 7 compliant as possible. This allowed
the ciphers IDEA, 3DES, CAST5,AES128, AES192, AES256, and TWOFISH.,
the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms
none and ZIP. This option implies @option{--escape-from-lines} and
disables @option{--throw-keyids},
@item --pgp8
@opindex pgp8
Set up all options to be as PGP 8 compliant as possible. PGP 8 is a lot
closer to the OpenPGP standard than previous versions of PGP, so all
this does is disable @option{--throw-keyids} and set
@option{--escape-from-lines}. All algorithms are allowed except for the
SHA224, SHA384, and SHA512 digests.
@item --compliance @var{string}
@opindex compliance
This option can be used instead of one of the options above. Valid
values for @var{string} are the above option names (without the double
dash) and possibly others as shown when using "help" for @var{value}.
@end table
@c *******************************************
@c ******** ESOTERIC OPTIONS ***************
@c *******************************************
@node GPG Esoteric Options
@subsection Doing things one usually doesn't want to do
@table @gnupgtabopt
@item -n
@itemx --dry-run
@opindex dry-run
Don't make any changes (this is not completely implemented).
@item --list-only
@opindex list-only
Changes the behaviour of some commands. This is like @option{--dry-run} but
different in some cases. The semantic of this option may be extended in
the future. Currently it only skips the actual decryption pass and
therefore enables a fast listing of the encryption keys.
@item -i
@itemx --interactive
@opindex interactive
Prompt before overwriting any files.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debug flags. All flags are or-ed and @var{flags} may be given
in C syntax (e.g. 0x0042) or as a comma separated list of flag names.
To get a list of all supported flags the single word "help" can be
used. This option is only useful for debugging and the behavior may
change at any time without notice.
@item --debug-all
@opindex debug-all
Set all useful debugging flags.
@item --debug-iolbf
@opindex debug-iolbf
Set stdout into line buffered mode. This option is only honored when
given on the command line.
@item --debug-set-iobuf-size @var{n}
@opindex debug-iolbf
Change the buffer size of the IOBUFs to @var{n} kilobyte. Using 0
prints the current size. Note well: This is a maintainer only option
and may thus be changed or removed at any time without notice.
@item --debug-allow-large-chunks
@opindex debug-allow-large-chunks
To facilitate in-memory decryption on the receiving site, the largest
recommended chunk size is 128 MiB (@code{--chunk-size 27}). This
option allows to specify a limit of up to 4 EiB (@code{--chunk-size
62}) for experiments.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back or
forth to @var{epoch} which is the number of seconds elapsed since the year
1970. Alternatively @var{epoch} may be given as a full ISO time string
(e.g. "20070924T154812").
If you suffix @var{epoch} with an exclamation mark (!), the system time
will appear to be frozen at the specified time.
@item --full-timestrings
@opindex full-timestrings
Change the format of printed creation and expiration times from just
the date to the date and time. This is in general not useful and the
same information is anyway available in @option{--with-colons} mode.
These longer strings are also not well aligned with other printed
data.
@item --enable-progress-filter
@opindex enable-progress-filter
Enable certain PROGRESS status outputs. This option allows frontends
to display a progress indicator while gpg is processing larger files.
There is a slight performance overhead using it.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}.
See the file DETAILS in the documentation for a listing of them.
@item --status-file @var{file}
@opindex status-file
Same as @option{--status-fd}, except the status data is written to file
@var{file}.
@item --logger-fd @var{n}
@opindex logger-fd
Write log output to file descriptor @var{n} and not to STDERR.
@item --log-file @var{file}
@itemx --logger-file @var{file}
@opindex log-file
Same as @option{--logger-fd}, except the logger data is written to
file @var{file}. Use @file{socket://} to log to s socket.
@item --attribute-fd @var{n}
@opindex attribute-fd
Write attribute subpackets to the file descriptor @var{n}. This is most
useful for use with @option{--status-fd}, since the status messages are
needed to separate out the various subpackets from the stream delivered
to the file descriptor.
@item --attribute-file @var{file}
@opindex attribute-file
Same as @option{--attribute-fd}, except the attribute data is written to
file @var{file}.
@item --comment @var{string}
@itemx --no-comments
@opindex comment
Use @var{string} as a comment string in cleartext signatures and ASCII
armored messages or keys (see @option{--armor}). The default behavior is
not to use a comment string. @option{--comment} may be repeated multiple
times to get multiple comment strings. @option{--no-comments} removes
all comments. It is a good idea to keep the length of a single comment
below 60 characters to avoid problems with mail programs wrapping such
lines. Note that comment lines, like all other header lines, are not
protected by the signature.
@item --emit-version
@itemx --no-emit-version
@opindex emit-version
Force inclusion of the version string in ASCII armored output. If
given once only the name of the program and the major number is
emitted, given twice the minor is also emitted, given thrice
the micro is added, and given four times an operating system identification
is also emitted. @option{--no-emit-version} (default) disables the version
line.
@item --sig-notation @{@var{name}=@var{value}@}
@itemx --cert-notation @{@var{name}=@var{value}@}
@itemx -N, --set-notation @{@var{name}=@var{value}@}
@opindex sig-notation
@opindex cert-notation
@opindex set-notation
Put the name value pair into the signature as notation data.
@var{name} must consist only of printable characters or spaces, and
must contain a '@@' character in the form keyname@@domain.example.com
(substituting the appropriate keyname and domain name, of course). This
is to help prevent pollution of the IETF reserved notation
namespace. The @option{--expert} flag overrides the '@@'
check. @var{value} may be any printable string; it will be encoded in
UTF-8, so you should check that your @option{--display-charset} is set
correctly. If you prefix @var{name} with an exclamation mark (!), the
notation data will be flagged as critical
(rfc4880:5.2.3.16). @option{--sig-notation} sets a notation for data
signatures. @option{--cert-notation} sets a notation for key signatures
(certifications). @option{--set-notation} sets both.
There are special codes that may be used in notation names. "%k" will
be expanded into the key ID of the key being signed, "%K" into the
long key ID of the key being signed, "%f" into the fingerprint of the
key being signed, "%s" into the key ID of the key making the
signature, "%S" into the long key ID of the key making the signature,
"%g" into the fingerprint of the key making the signature (which might
be a subkey), "%p" into the fingerprint of the primary key of the key
making the signature, "%c" into the signature count from the OpenPGP
smartcard, and "%%" results in a single "%". %k, %K, and %f are only
meaningful when making a key signature (certification), and %c is only
meaningful when using the OpenPGP smartcard.
@item --known-notation @var{name}
@opindex known-notation
Adds @var{name} to a list of known critical signature notations. The
effect of this is that gpg will not mark a signature with a critical
signature notation of that name as bad. Note that gpg already knows
by default about a few critical signatures notation names.
@item --sig-policy-url @var{string}
@itemx --cert-policy-url @var{string}
@itemx --set-policy-url @var{string}
@opindex sig-policy-url
@opindex cert-policy-url
@opindex set-policy-url
Use @var{string} as a Policy URL for signatures (rfc4880:5.2.3.20). If
you prefix it with an exclamation mark (!), the policy URL packet will
be flagged as critical. @option{--sig-policy-url} sets a policy url for
data signatures. @option{--cert-policy-url} sets a policy url for key
signatures (certifications). @option{--set-policy-url} sets both.
The same %-expandos used for notation data are available here as well.
@item --sig-keyserver-url @var{string}
@opindex sig-keyserver-url
Use @var{string} as a preferred keyserver URL for data signatures. If
you prefix it with an exclamation mark (!), the keyserver URL packet
will be flagged as critical.
The same %-expandos used for notation data are available here as well.
@item --set-filename @var{string}
@opindex set-filename
Use @var{string} as the filename which is stored inside messages.
This overrides the default, which is to use the actual filename of the
file being encrypted. Using the empty string for @var{string}
effectively removes the filename from the output.
@item --for-your-eyes-only
@itemx --no-for-your-eyes-only
@opindex for-your-eyes-only
Set the `for your eyes only' flag in the message. This causes GnuPG to
refuse to save the file unless the @option{--output} option is given,
and PGP to use a "secure viewer" with a claimed Tempest-resistant font
to display the message. This option overrides @option{--set-filename}.
@option{--no-for-your-eyes-only} disables this option.
@item --use-embedded-filename
@itemx --no-use-embedded-filename
@opindex use-embedded-filename
Try to create a file with a name as embedded in the data. This can be
a dangerous option as it enables overwriting files. Defaults to no.
Note that the option @option{--output} overrides this option.
@item --cipher-algo @var{name}
@opindex cipher-algo
Use @var{name} as cipher algorithm. Running the program with the
command @option{--version} yields a list of supported algorithms. If
this is not used the cipher algorithm is selected from the preferences
stored with the key. In general, you do not want to use this option as
it allows you to violate the OpenPGP standard. The option
@option{--personal-cipher-preferences} is the safe way to accomplish the
same thing.
@item --aead-algo @var{name}
@opindex aead-algo
Specify that the AEAD algorithm @var{name} is to be used. This is
useful for symmetric encryption where no key preference are available
to select the AEAD algorithm. Running @command{@gpgname} with option
@option{--version} shows the available AEAD algorithms. In general,
you do not want to use this option as it allows you to violate the
OpenPGP standard. The option @option{--personal-aead-preferences} is
the safe way to accomplish the same thing.
@item --digest-algo @var{name}
@opindex digest-algo
Use @var{name} as the message digest algorithm. Running the program
with the command @option{--version} yields a list of supported
algorithms. In general, you do not want to use this option as it
allows you to violate the OpenPGP standard. The option
@option{--personal-digest-preferences} is the safe way to accomplish
the same thing.
@item --compress-algo @var{name}
@opindex compress-algo
Use compression algorithm @var{name}. "zlib" is RFC-1950 ZLIB
compression. "zip" is RFC-1951 ZIP compression which is used by PGP.
"bzip2" is a more modern compression scheme that can compress some
things better than zip or zlib, but at the cost of more memory used
during compression and decompression. "uncompressed" or "none"
disables compression. If this option is not used, the default
behavior is to examine the recipient key preferences to see which
algorithms the recipient supports. If all else fails, ZIP is used for
maximum compatibility.
ZLIB may give better compression results than ZIP, as the compression
window size is not limited to 8k. BZIP2 may give even better
compression results than that, but will use a significantly larger
amount of memory while compressing and decompressing. This may be
significant in low memory situations. Note, however, that PGP (all
versions) only supports ZIP compression. Using any algorithm other
than ZIP or "none" will make the message unreadable with PGP. In
general, you do not want to use this option as it allows you to
violate the OpenPGP standard. The option
@option{--personal-compress-preferences} is the safe way to accomplish
the same thing.
@item --cert-digest-algo @var{name}
@opindex cert-digest-algo
Use @var{name} as the message digest algorithm used when signing a
key. Running the program with the command @option{--version} yields a
list of supported algorithms. Be aware that if you choose an
algorithm that GnuPG supports but other OpenPGP implementations do
not, then some users will not be able to use the key signatures you
make, or quite possibly your entire key. Note also that a public key
algorithm must be compatible with the specified digest algorithm; thus
selecting an arbitrary digest algorithm may result in error messages
from lower crypto layers or lead to security flaws.
@item --disable-cipher-algo @var{name}
@opindex disable-cipher-algo
Never allow the use of @var{name} as cipher algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --disable-pubkey-algo @var{name}
@opindex disable-pubkey-algo
Never allow the use of @var{name} as public key algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --throw-keyids
@itemx --no-throw-keyids
@opindex throw-keyids
Do not put the recipient key IDs into encrypted messages. This helps to
hide the receivers of the message and is a limited countermeasure
against traffic analysis.@footnote{Using a little social engineering
anyone who is able to decrypt the message can check whether one of the
other recipients is the one he suspects.} On the receiving side, it may
slow down the decryption process because all available secret keys must
be tried. @option{--no-throw-keyids} disables this option. This option
is essentially the same as using @option{--hidden-recipient} for all
recipients.
@item --not-dash-escaped
@opindex not-dash-escaped
This option changes the behavior of cleartext signatures
so that they can be used for patch files. You should not
send such an armored file via email because all spaces
and line endings are hashed too. You can not use this
option for data which has 5 dashes at the beginning of a
line, patch files don't have this. A special armor header
line tells GnuPG about this cleartext signature option.
@item --escape-from-lines
@itemx --no-escape-from-lines
@opindex escape-from-lines
Because some mailers change lines starting with "From " to ">From " it
is good to handle such lines in a special way when creating cleartext
signatures to prevent the mail system from breaking the signature. Note
that all other PGP versions do it this way too. Enabled by
default. @option{--no-escape-from-lines} disables this option.
@item --passphrase-repeat @var{n}
@opindex passphrase-repeat
Specify how many times @command{@gpgname} will request a new
passphrase be repeated. This is useful for helping memorize a
passphrase. Defaults to 1 repetition.
@item --passphrase-fd @var{n}
@opindex passphrase-fd
Read the passphrase from file descriptor @var{n}. Only the first line
will be read from file descriptor @var{n}. If you use 0 for @var{n},
the passphrase will be read from STDIN. This can only be used if only
one passphrase is supplied.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase-file @var{file}
@opindex passphrase-file
Read the passphrase from file @var{file}. Only the first line will
be read from file @var{file}. This can only be used if only one
passphrase is supplied. Obviously, a passphrase stored in a file is
of questionable security if other users can read this file. Don't use
this option if you can avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase @var{string}
@opindex passphrase
Use @var{string} as the passphrase. This can only be used if only one
passphrase is supplied. Obviously, this is of very questionable
security on a multi-user system. Don't use this option if you can
avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --pinentry-mode @var{mode}
@opindex pinentry-mode
Set the pinentry mode to @var{mode}. Allowed values for @var{mode}
are:
@table @asis
@item default
Use the default of the agent, which is @code{ask}.
@item ask
Force the use of the Pinentry.
@item cancel
Emulate use of Pinentry's cancel button.
@item error
Return a Pinentry error (``No Pinentry'').
@item loopback
Redirect Pinentry queries to the caller. Note that in contrast to
Pinentry the user is not prompted again if he enters a bad password.
@end table
@item --no-symkey-cache
@opindex no-symkey-cache
Disable the passphrase cache used for symmetrical en- and decryption.
This cache is based on the message specific salt value
(cf. @option{--s2k-mode}).
@item --request-origin @var{origin}
@opindex request-origin
Tell gpg to assume that the operation ultimately originated at
@var{origin}. Depending on the origin certain restrictions are applied
and the Pinentry may include an extra note on the origin. Supported
values for @var{origin} are: @code{local} which is the default,
@code{remote} to indicate a remote origin or @code{browser} for an
operation requested by a web browser.
@item --command-fd @var{n}
@opindex command-fd
This is a replacement for the deprecated shared-memory IPC mode.
If this option is enabled, user input on questions is not expected
from the TTY but from the given file descriptor. It should be used
together with @option{--status-fd}. See the file doc/DETAILS in the source
distribution for details on how to use it.
@item --command-file @var{file}
@opindex command-file
Same as @option{--command-fd}, except the commands are read out of file
@var{file}
@item --allow-non-selfsigned-uid
@itemx --no-allow-non-selfsigned-uid
@opindex allow-non-selfsigned-uid
Allow the import and use of keys with user IDs which are not
self-signed. This is not recommended, as a non self-signed user ID is
trivial to forge. @option{--no-allow-non-selfsigned-uid} disables.
@item --allow-freeform-uid
@opindex allow-freeform-uid
Disable all checks on the form of the user ID while generating a new
one. This option should only be used in very special environments as
it does not ensure the de-facto standard format of user IDs.
@item --ignore-time-conflict
@opindex ignore-time-conflict
GnuPG normally checks that the timestamps associated with keys and
signatures have plausible values. However, sometimes a signature
seems to be older than the key due to clock problems. This option
makes these checks just a warning. See also @option{--ignore-valid-from} for
timestamp issues on subkeys.
@item --ignore-valid-from
@opindex ignore-valid-from
GnuPG normally does not select and use subkeys created in the future.
This option allows the use of such keys and thus exhibits the
pre-1.0.7 behaviour. You should not use this option unless there
is some clock problem. See also @option{--ignore-time-conflict} for timestamp
issues with signatures.
@item --ignore-crc-error
@opindex ignore-crc-error
The ASCII armor used by OpenPGP is protected by a CRC checksum against
transmission errors. Occasionally the CRC gets mangled somewhere on
the transmission channel but the actual content (which is protected by
the OpenPGP protocol anyway) is still okay. This option allows GnuPG
to ignore CRC errors.
@item --ignore-mdc-error
@opindex ignore-mdc-error
This option changes a MDC integrity protection failure into a warning.
It is required to decrypt old messages which did not use an MDC. It
may also be useful if a message is partially garbled, but it is
necessary to get as much data as possible out of that garbled message.
Be aware that a missing or failed MDC can be an indication of an
attack. Use with great caution; see also option @option{--rfc2440}.
@item --allow-weak-digest-algos
@opindex allow-weak-digest-algos
Signatures made with known-weak digest algorithms are normally
rejected with an ``invalid digest algorithm'' message. This option
allows the verification of signatures made with such weak algorithms.
MD5 is the only digest algorithm considered weak by default. See also
@option{--weak-digest} to reject other digest algorithms.
@item --weak-digest @var{name}
@opindex weak-digest
Treat the specified digest algorithm as weak. Signatures made over
weak digests algorithms are normally rejected. This option can be
supplied multiple times if multiple algorithms should be considered
weak. See also @option{--allow-weak-digest-algos} to disable
rejection of weak digests. MD5 is always considered weak, and does
not need to be listed explicitly.
@item --allow-weak-key-signatures
@opindex allow-weak-key-signatures
To avoid a minor risk of collision attacks on third-party key
signatures made using SHA-1, those key signatures are considered
invalid. This options allows to override this restriction.
@item --no-default-keyring
@opindex no-default-keyring
Do not add the default keyrings to the list of keyrings. Note that
GnuPG will not operate without any keyrings, so if you use this option
and do not provide alternate keyrings via @option{--keyring} or
@option{--secret-keyring}, then GnuPG will still use the default public or
secret keyrings.
@item --no-keyring
@opindex no-keyring
Do not use any keyring at all. This overrides the default and all
options which specify keyrings.
@item --skip-verify
@opindex skip-verify
Skip the signature verification step. This may be
used to make the decryption faster if the signature
verification is not needed.
@item --with-key-data
@opindex with-key-data
Print key listings delimited by colons (like @option{--with-colons}) and
print the public key data.
@item --list-signatures
@opindex list-signatures
@itemx --list-sigs
@opindex list-sigs
Same as @option{--list-keys}, but the signatures are listed too. This
command has the same effect as using @option{--list-keys} with
@option{--with-sig-list}. Note that in contrast to
@option{--check-signatures} the key signatures are not verified. This
command can be used to create a list of signing keys missing in the
local keyring; for example:
@example
gpg --list-sigs --with-colons USERID | \
awk -F: '$1=="sig" && $2=="?" @{if($13)@{print $13@}else@{print $5@}@}'
@end example
@item --fast-list-mode
@opindex fast-list-mode
Changes the output of the list commands to work faster; this is achieved
by leaving some parts empty. Some applications don't need the user ID
and the trust information given in the listings. By using this options
they can get a faster listing. The exact behaviour of this option may
change in future versions. If you are missing some information, don't
use this option.
@item --no-literal
@opindex no-literal
This is not for normal use. Use the source to see for what it might be useful.
@item --set-filesize
@opindex set-filesize
This is not for normal use. Use the source to see for what it might be useful.
@item --show-session-key
@opindex show-session-key
Display the session key used for one message. See
@option{--override-session-key} for the counterpart of this option.
We think that Key Escrow is a Bad Thing; however the user should have
the freedom to decide whether to go to prison or to reveal the content
of one specific message without compromising all messages ever
encrypted for one secret key.
You can also use this option if you receive an encrypted message which
is abusive or offensive, to prove to the administrators of the
messaging system that the ciphertext transmitted corresponds to an
inappropriate plaintext so they can take action against the offending
user.
@item --override-session-key @var{string}
@itemx --override-session-key-fd @var{fd}
@opindex override-session-key
Don't use the public key but the session key @var{string} respective
the session key taken from the first line read from file descriptor
@var{fd}. The format of this string is the same as the one printed by
@option{--show-session-key}. This option is normally not used but
comes handy in case someone forces you to reveal the content of an
encrypted message; using this option you can do this without handing
out the secret key. Note that using @option{--override-session-key}
may reveal the session key to all local users via the global process
table. Often it is useful to combine this option with
@option{--no-keyring}.
@item --ask-sig-expire
@itemx --no-ask-sig-expire
@opindex ask-sig-expire
When making a data signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-sig-expire} is used. @option{--no-ask-sig-expire}
disables this option.
@item --default-sig-expire
@opindex default-sig-expire
The default expiration time to use for signature expiration. Valid
values are "0" for no expiration, a number followed by the letter d
(for days), w (for weeks), m (for months), or y (for years) (for
example "2m" for two months, or "5y" for five years), or an absolute
date in the form YYYY-MM-DD. Defaults to "0".
@item --ask-cert-expire
@itemx --no-ask-cert-expire
@opindex ask-cert-expire
When making a key signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-cert-expire} is used. @option{--no-ask-cert-expire}
disables this option.
@item --default-cert-expire
@opindex default-cert-expire
The default expiration time to use for key signature expiration.
Valid values are "0" for no expiration, a number followed by the
letter d (for days), w (for weeks), m (for months), or y (for years)
(for example "2m" for two months, or "5y" for five years), or an
absolute date in the form YYYY-MM-DD. Defaults to "0".
@item --default-new-key-algo @var{string}
@opindex default-new-key-algo @var{string}
This option can be used to change the default algorithms for key
generation. The @var{string} is similar to the arguments required for
the command @option{--quick-add-key} but slightly different. For
example the current default of @code{"rsa2048/cert,sign+rsa2048/encr"}
(or @code{"rsa3072"}) can be changed to the value of what we currently
call future default, which is @code{"ed25519/cert,sign+cv25519/encr"}.
You need to consult the source code to learn the details. Note that
the advanced key generation commands can always be used to specify a
key algorithm directly.
@item --allow-secret-key-import
@opindex allow-secret-key-import
This is an obsolete option and is not used anywhere.
@item --allow-multiple-messages
@item --no-allow-multiple-messages
These are obsolete options; they have no more effect since GnuPG 2.2.8.
@item --enable-special-filenames
@opindex enable-special-filenames
This option enables a mode in which filenames of the form
@file{-&n}, where n is a non-negative decimal number,
refer to the file descriptor n and not to a file with that name.
@item --no-expensive-trust-checks
@opindex no-expensive-trust-checks
Experimental use only.
@item --preserve-permissions
@opindex preserve-permissions
Don't change the permissions of a secret keyring back to user
read/write only. Use this option only if you really know what you are doing.
@item --default-preference-list @var{string}
@opindex default-preference-list
Set the list of default preferences to @var{string}. This preference
list is used for new keys and becomes the default for "setpref" in the
edit menu.
@item --default-keyserver-url @var{name}
@opindex default-keyserver-url
Set the default keyserver URL to @var{name}. This keyserver will be
used as the keyserver URL when writing a new self-signature on a key,
which includes key generation and changing preferences.
@item --list-config
@opindex list-config
Display various internal configuration parameters of GnuPG. This option
is intended for external programs that call GnuPG to perform tasks, and
is thus not generally useful. See the file @file{doc/DETAILS} in the
source distribution for the details of which configuration items may be
listed. @option{--list-config} is only usable with
@option{--with-colons} set.
@item --list-gcrypt-config
@opindex list-gcrypt-config
Display various internal configuration parameters of Libgcrypt.
@item --gpgconf-list
@opindex gpgconf-list
This command is similar to @option{--list-config} but in general only
internally used by the @command{gpgconf} tool.
@item --gpgconf-test
@opindex gpgconf-test
This is more or less dummy action. However it parses the configuration
file and returns with failure if the configuration file would prevent
@command{@gpgname} from startup. Thus it may be used to run a syntax check
on the configuration file.
@c @item --use-only-openpgp-card
@c @opindex use-only-openpgp-card
@c Only access OpenPGP card's and no other cards. This is a hidden
@c option which could be used in case an old use case required the
-@c OpenPGP card while several cards are avaiable. This option might be
+@c OpenPGP card while several cards are available. This option might be
@c removed if it turns out that nobody requires it.
@end table
@c *******************************
@c ******* Deprecated ************
@c *******************************
@node Deprecated Options
@subsection Deprecated options
@table @gnupgtabopt
@item --show-photos
@itemx --no-show-photos
@opindex show-photos
Causes @option{--list-keys}, @option{--list-signatures},
@option{--list-public-keys}, @option{--list-secret-keys}, and verifying
a signature to also display the photo ID attached to the key, if
any. See also @option{--photo-viewer}. These options are deprecated. Use
@option{--list-options [no-]show-photos} and/or @option{--verify-options
[no-]show-photos} instead.
@item --show-keyring
@opindex show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. This option is deprecated: use
@option{--list-options [no-]show-keyring} instead.
@item --always-trust
@opindex always-trust
Identical to @option{--trust-model always}. This option is deprecated.
@item --show-notation
@itemx --no-show-notation
@opindex show-notation
Show signature notations in the @option{--list-signatures} or @option{--check-signatures} listings
as well as when verifying a signature with a notation in it. These
options are deprecated. Use @option{--list-options [no-]show-notation}
and/or @option{--verify-options [no-]show-notation} instead.
@item --show-policy-url
@itemx --no-show-policy-url
@opindex show-policy-url
Show policy URLs in the @option{--list-signatures} or @option{--check-signatures}
listings as well as when verifying a signature with a policy URL in
it. These options are deprecated. Use @option{--list-options
[no-]show-policy-url} and/or @option{--verify-options
[no-]show-policy-url} instead.
@end table
@c *******************************************
@c *************** ****************
@c *************** FILES ****************
@c *************** ****************
@c *******************************************
@mansect files
@node GPG Configuration
@section Configuration files
There are a few configuration files to control certain aspects of
@command{@gpgname}'s operation. Unless noted, they are expected in the
current home directory (@pxref{option --homedir}).
@table @file
@item gpg.conf
@efindex gpg.conf
This is the standard configuration file read by @command{@gpgname} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This default
name may be changed on the command line (@pxref{gpg-option --options}).
You should backup this file.
@end table
Note that on larger installations, it is useful to put predefined files
into the directory @file{@value{SYSCONFSKELDIR}} so that
newly created users start up with a working configuration.
For existing users a small
helper script is provided to create these files (@pxref{addgnupghome}).
For internal purposes @command{@gpgname} creates and maintains a few other
files; They all live in the current home directory (@pxref{option
--homedir}). Only the @command{@gpgname} program may modify these files.
@table @file
@item ~/.gnupg
@efindex ~/.gnupg
This is the default home directory which is used if neither the
environment variable @code{GNUPGHOME} nor the option
@option{--homedir} is given.
@item ~/.gnupg/pubring.gpg
@efindex pubring.gpg
The public keyring. You should backup this file.
@item ~/.gnupg/pubring.gpg.lock
The lock file for the public keyring.
@item ~/.gnupg/pubring.kbx
@efindex pubring.kbx
The public keyring using a different format. This file is shared
with @command{gpgsm}. You should backup this file.
@item ~/.gnupg/pubring.kbx.lock
The lock file for @file{pubring.kbx}.
@item ~/.gnupg/secring.gpg
@efindex secring.gpg
A secret keyring as used by GnuPG versions before 2.1. It is not
used by GnuPG 2.1 and later.
@item ~/.gnupg/secring.gpg.lock
The lock file for the secret keyring.
@item ~/.gnupg/.gpg-v21-migrated
@efindex .gpg-v21-migrated
File indicating that a migration to GnuPG 2.1 has been done.
@item ~/.gnupg/trustdb.gpg
@efindex trustdb.gpg
The trust database. There is no need to backup this file; it is better
to backup the ownertrust values (@pxref{option --export-ownertrust}).
@item ~/.gnupg/trustdb.gpg.lock
The lock file for the trust database.
@item ~/.gnupg/random_seed
@efindex random_seed
A file used to preserve the state of the internal random pool.
@item ~/.gnupg/openpgp-revocs.d/
@efindex openpgp-revocs.d
This is the directory where gpg stores pre-generated revocation
certificates. The file name corresponds to the OpenPGP fingerprint of
the respective key. It is suggested to backup those certificates and
if the primary private key is not stored on the disk to move them to
an external storage device. Anyone who can access these files is
able to revoke the corresponding key. You may want to print them out.
You should backup all files in this directory and take care to keep
this backup closed away.
@end table
Operation is further controlled by a few environment variables:
@table @asis
@item HOME
@efindex HOME
Used to locate the default home directory.
@item GNUPGHOME
@efindex GNUPGHOME
If set directory used instead of "~/.gnupg".
@item GPG_AGENT_INFO
This variable is obsolete; it was used by GnuPG versions before 2.1.
@item PINENTRY_USER_DATA
@efindex PINENTRY_USER_DATA
This value is passed via gpg-agent to pinentry. It is useful to convey
extra information to a custom pinentry.
@item COLUMNS
@itemx LINES
@efindex COLUMNS
@efindex LINES
Used to size some displays to the full size of the screen.
@item LANGUAGE
@efindex LANGUAGE
Apart from its use by GNU, it is used in the W32 version to override the
language selection done through the Registry. If used and set to a
valid and available language name (@var{langid}), the file with the
translation is loaded from
@code{@var{gpgdir}/gnupg.nls/@var{langid}.mo}. Here @var{gpgdir} is the
directory out of which the gpg binary has been loaded. If it can't be
loaded the Registry is tried and as last resort the native Windows
locale system is used.
@end table
When calling the gpg-agent component @command{@gpgname} sends a set of
environment variables to gpg-agent. The names of these variables can
be listed using the command:
@example
gpg-connect-agent 'getinfo std_env_names' /bye | awk '$1=="D" @{print $2@}'
@end example
@c *******************************************
@c *************** ****************
@c *************** EXAMPLES ****************
@c *************** ****************
@c *******************************************
@mansect examples
@node GPG Examples
@section Examples
@table @asis
@item gpg -se -r @code{Bob} @code{file}
sign and encrypt for user Bob
@item gpg --clear-sign @code{file}
make a cleartext signature
@item gpg -sb @code{file}
make a detached signature
@item gpg -u 0x12345678 -sb @code{file}
make a detached signature with the key 0x12345678
@item gpg --list-keys @code{user_ID}
show keys
@item gpg --fingerprint @code{user_ID}
show fingerprint
@item gpg --verify @code{pgpfile}
@itemx gpg --verify @code{sigfile} [@code{datafile}]
Verify the signature of the file but do not output the data unless
requested. The second form is used for detached signatures, where
@code{sigfile} is the detached signature (either ASCII armored or
binary) and @code{datafile} are the signed data; if this is not given, the name of the
file holding the signed data is constructed by cutting off the
extension (".asc" or ".sig") of @code{sigfile} or by asking the user
for the filename. If the option @option{--output} is also used the
signed data is written to the file specified by that option; use
@code{-} to write the signed data to stdout.
@end table
@c *******************************************
@c *************** ****************
@c *************** USER ID ****************
@c *************** ****************
@c *******************************************
@mansect how to specify a user id
@ifset isman
@include specify-user-id.texi
@end ifset
@mansect filter expressions
@chapheading FILTER EXPRESSIONS
The options @option{--import-filter} and @option{--export-filter} use
expressions with this syntax (square brackets indicate an optional
part and curly braces a repetition, white space between the elements
are allowed):
@c man:.RS
@example
[lc] @{[@{flag@}] PROPNAME op VALUE [lc]@}
@end example
@c man:.RE
The name of a property (@var{PROPNAME}) may only consist of letters,
digits and underscores. The description for the filter type
describes which properties are defined. If an undefined property is
used it evaluates to the empty string. Unless otherwise noted, the
@var{VALUE} must always be given and may not be the empty string. No
quoting is defined for the value, thus the value may not contain the
strings @code{&&} or @code{||}, which are used as logical connection
operators. The flag @code{--} can be used to remove this restriction.
Numerical values are computed as long int; standard C notation
applies. @var{lc} is the logical connection operator; either
@code{&&} for a conjunction or @code{||} for a disjunction. A
conjunction is assumed at the begin of an expression. Conjunctions
have higher precedence than disjunctions. If @var{VALUE} starts with
one of the characters used in any @var{op} a space after the
@var{op} is required.
@noindent
The supported operators (@var{op}) are:
@table @asis
@item =~
Substring must match.
@item !~
Substring must not match.
@item =
The full string must match.
@item <>
The full string must not match.
@item ==
The numerical value must match.
@item !=
The numerical value must not match.
@item <=
The numerical value of the field must be LE than the value.
@item <
The numerical value of the field must be LT than the value.
@item >
The numerical value of the field must be GT than the value.
@item >=
The numerical value of the field must be GE than the value.
@item -le
The string value of the field must be less or equal than the value.
@item -lt
The string value of the field must be less than the value.
@item -gt
The string value of the field must be greater than the value.
@item -ge
The string value of the field must be greater or equal than the value.
@item -n
True if value is not empty (no value allowed).
@item -z
True if value is empty (no value allowed).
@item -t
Alias for "PROPNAME != 0" (no value allowed).
@item -f
Alias for "PROPNAME == 0" (no value allowed).
@end table
@noindent
Values for @var{flag} must be space separated. The supported flags
are:
@table @asis
@item --
@var{VALUE} spans to the end of the expression.
@item -c
The string match in this part is done case-sensitive.
@end table
The filter options concatenate several specifications for a filter of
the same type. For example the four options in this example:
@c man:.RS
@example
--import-filter keep-uid="uid =~ Alfa"
--import-filter keep-uid="&& uid !~ Test"
--import-filter keep-uid="|| uid =~ Alpha"
--import-filter keep-uid="uid !~ Test"
@end example
@c man:.RE
@noindent
which is equivalent to
@c man:.RS
@example
--import-filter \
keep-uid="uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
@end example
@c man:.RE
imports only the user ids of a key containing the strings "Alfa"
or "Alpha" but not the string "test".
@mansect trust values
@ifset isman
@include trust-values.texi
@end ifset
@mansect return value
@chapheading RETURN VALUE
The program returns 0 if there are no severe errors, 1 if at least a
signature was bad, and other error codes for fatal errors.
Note that signature verification requires exact knowledge of what has
-been signed and by whom it has beensigned. Using only the return code
+been signed and by whom it has been signed. Using only the return code
is thus not an appropriate way to verify a signature by a script.
Either make proper use or the status codes or use the @command{gpgv}
tool which has been designed to make signature verification easy for
scripts.
@mansect warnings
@chapheading WARNINGS
Use a good password for your user account and make sure that all
security issues are always fixed on your machine. Also employ
diligent physical protection to your machine. Consider to use a good
passphrase as a last resort protection to your secret key in the case
your machine gets stolen. It is important that your secret key is
never leaked. Using an easy to carry around token or smartcard with
the secret key is often a advisable.
If you are going to verify detached signatures, make sure that the
program knows about it; either give both filenames on the command line
or use @samp{-} to specify STDIN.
For scripted or other unattended use of @command{gpg} make sure to use
the machine-parseable interface and not the default interface which is
intended for direct use by humans. The machine-parseable interface
provides a stable and well documented API independent of the locale or
future changes of @command{gpg}. To enable this interface use the
options @option{--with-colons} and @option{--status-fd}. For certain
operations the option @option{--command-fd} may come handy too. See
this man page and the file @file{DETAILS} for the specification of the
interface. Note that the GnuPG ``info'' pages as well as the PDF
version of the GnuPG manual features a chapter on unattended use of
GnuPG. As an alternative the library @command{GPGME} can be used as a
high-level abstraction on top of that interface.
@mansect interoperability
@chapheading INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS
GnuPG tries to be a very flexible implementation of the OpenPGP
standard. In particular, GnuPG implements many of the optional parts
of the standard, such as the SHA-512 hash, and the ZLIB and BZIP2
compression algorithms. It is important to be aware that not all
OpenPGP programs implement these optional algorithms and that by
forcing their use via the @option{--cipher-algo},
@option{--digest-algo}, @option{--cert-digest-algo}, or
@option{--compress-algo} options in GnuPG, it is possible to create a
perfectly valid OpenPGP message, but one that cannot be read by the
intended recipient.
There are dozens of variations of OpenPGP programs available, and each
supports a slightly different subset of these optional algorithms.
For example, until recently, no (unhacked) version of PGP supported
the BLOWFISH cipher algorithm. A message using BLOWFISH simply could
not be read by a PGP user. By default, GnuPG uses the standard
OpenPGP preferences system that will always do the right thing and
create messages that are usable by all recipients, regardless of which
OpenPGP program they use. Only override this safe default if you
really know what you are doing.
If you absolutely must override the safe default, or if the preferences
on a given key are invalid for some reason, you are far better off using
the @option{--pgp6}, @option{--pgp7}, or @option{--pgp8} options. These
options are safe as they do not force any particular algorithms in
violation of OpenPGP, but rather reduce the available algorithms to a
"PGP-safe" list.
@mansect bugs
@chapheading BUGS
On older systems this program should be installed as setuid(root). This
is necessary to lock memory pages. Locking memory pages prevents the
operating system from writing memory pages (which may contain
passphrases or other sensitive material) to disk. If you get no
warning message about insecure memory your operating system supports
locking without being root. The program drops root privileges as soon
as locked memory is allocated.
Note also that some systems (especially laptops) have the ability to
``suspend to disk'' (also known as ``safe sleep'' or ``hibernate'').
This writes all memory to disk before going into a low power or even
powered off mode. Unless measures are taken in the operating system
to protect the saved memory, passphrases or other sensitive material
may be recoverable from it later.
Before you report a bug you should first search the mailing list
archives for similar problems and second check whether such a bug has
already been reported to our bug tracker at @url{https://bugs.gnupg.org}.
@c *******************************************
@c *************** **************
@c *************** UNATTENDED **************
@c *************** **************
@c *******************************************
@manpause
@node Unattended Usage of GPG
@section Unattended Usage
@command{@gpgname} is often used as a backend engine by other software. To help
with this a machine interface has been defined to have an unambiguous
way to do this. The options @option{--status-fd} and @option{--batch}
are almost always required for this.
@menu
* Programmatic use of GnuPG:: Programmatic use of GnuPG
* Ephemeral home directories:: Ephemeral home directories
* The quick key manipulation interface:: The quick key manipulation interface
* Unattended GPG key generation:: Unattended key generation
@end menu
@node Programmatic use of GnuPG
@subsection Programmatic use of GnuPG
Please consider using GPGME instead of calling @command{@gpgname}
directly. GPGME offers a stable, backend-independent interface for
many cryptographic operations. It supports OpenPGP and S/MIME, and
also allows interaction with various GnuPG components.
GPGME provides a C-API, and comes with bindings for C++, Qt, and
Python. Bindings for other languages are available.
@node Ephemeral home directories
@subsection Ephemeral home directories
Sometimes you want to contain effects of some operation, for example
you want to import a key to inspect it, but you do not want this key
to be added to your keyring. In earlier versions of GnuPG, it was
possible to specify alternate keyring files for both public and secret
keys. In modern GnuPG versions, however, we changed how secret keys
are stored in order to better protect secret key material, and it was
not possible to preserve this interface.
The preferred way to do this is to use ephemeral home directories.
This technique works across all versions of GnuPG.
Create a temporary directory, create (or copy) a configuration that
meets your needs, make @command{@gpgname} use this directory either
using the environment variable @var{GNUPGHOME}, or the option
@option{--homedir}. GPGME supports this too on a per-context basis,
by modifying the engine info of contexts. Now execute whatever
operation you like, import and export key material as necessary. Once
finished, you can delete the directory. All GnuPG backend services
that were started will detect this and shut down.
@node The quick key manipulation interface
@subsection The quick key manipulation interface
Recent versions of GnuPG have an interface to manipulate keys without
using the interactive command @option{--edit-key}. This interface was
added mainly for the benefit of GPGME (please consider using GPGME,
see the manual subsection ``Programmatic use of GnuPG''). This
interface is described in the subsection ``How to manage your keys''.
@node Unattended GPG key generation
@subsection Unattended key generation
The command @option{--generate-key} may be used along with the option
@option{--batch} for unattended key generation. This is the most
flexible way of generating keys, but it is also the most complex one.
Consider using the quick key manipulation interface described in the
previous subsection ``The quick key manipulation interface''.
The parameters for the key are either read from stdin or given as a
file on the command line. The format of the parameter file is as
follows:
@itemize @bullet
@item Text only, line length is limited to about 1000 characters.
@item UTF-8 encoding must be used to specify non-ASCII characters.
@item Empty lines are ignored.
@item Leading and trailing white space is ignored.
@item A hash sign as the first non white space character indicates
a comment line.
@item Control statements are indicated by a leading percent sign, the
arguments are separated by white space from the keyword.
@item Parameters are specified by a keyword, followed by a colon. Arguments
are separated by white space.
@item
The first parameter must be @samp{Key-Type}; control statements may be
placed anywhere.
@item
The order of the parameters does not matter except for @samp{Key-Type}
which must be the first parameter. The parameters are only used for
the generated keyblock (primary and subkeys); parameters from previous
sets are not used. Some syntactically checks may be performed.
@item
Key generation takes place when either the end of the parameter file
is reached, the next @samp{Key-Type} parameter is encountered or at the
control statement @samp{%commit} is encountered.
@end itemize
@noindent
Control statements:
@table @asis
@item %echo @var{text}
Print @var{text} as diagnostic.
@item %dry-run
Suppress actual key generation (useful for syntax checking).
@item %commit
Perform the key generation. Note that an implicit commit is done at
the next @asis{Key-Type} parameter.
@item %pubring @var{filename}
Do not write the key to the default or commandline given keyring but
to @var{filename}. This must be given before the first commit to take
place, duplicate specification of the same filename is ignored, the
last filename before a commit is used. The filename is used until a
new filename is used (at commit points) and all keys are written to
that file. If a new filename is given, this file is created (and
overwrites an existing one).
See the previous subsection ``Ephemeral home directories'' for a more
robust way to contain side-effects.
@item %secring @var{filename}
This option is a no-op for GnuPG 2.1 and later.
See the previous subsection ``Ephemeral home directories''.
@item %ask-passphrase
@itemx %no-ask-passphrase
This option is a no-op for GnuPG 2.1 and later.
@item %no-protection
Using this option allows the creation of keys without any passphrase
protection. This option is mainly intended for regression tests.
@item %transient-key
If given the keys are created using a faster and a somewhat less
secure random number generator. This option may be used for keys
which are only used for a short time and do not require full
cryptographic strength. It takes only effect if used together with
the control statement @samp{%no-protection}.
@end table
@noindent
General Parameters:
@table @asis
@item Key-Type: @var{algo}
Starts a new parameter block by giving the type of the primary
key. The algorithm must be capable of signing. This is a required
parameter. @var{algo} may either be an OpenPGP algorithm number or a
string with the algorithm name. The special value @samp{default} may
be used for @var{algo} to create the default key type; in this case a
@samp{Key-Usage} shall not be given and @samp{default} also be used
for @samp{Subkey-Type}.
@item Key-Length: @var{nbits}
The requested length of the generated key in bits. The default is
returned by running the command @samp{@gpgname --gpgconf-list}.
@item Key-Grip: @var{hexstring}
This is optional and used to generate a CSR or certificate for an
already existing key. Key-Length will be ignored when given.
@item Key-Usage: @var{usage-list}
Space or comma delimited list of key usages. Allowed values are
@samp{encrypt}, @samp{sign}, and @samp{auth}. This is used to
generate the key flags. Please make sure that the algorithm is
capable of this usage. Note that OpenPGP requires that all primary
keys are capable of certification, so no matter what usage is given
here, the @samp{cert} flag will be on. If no @samp{Key-Usage} is
specified and the @samp{Key-Type} is not @samp{default}, all allowed
usages for that particular algorithm are used; if it is not given but
@samp{default} is used the usage will be @samp{sign}.
@item Subkey-Type: @var{algo}
This generates a secondary key (subkey). Currently only one subkey
can be handled. See also @samp{Key-Type} above.
@item Subkey-Length: @var{nbits}
Length of the secondary key (subkey) in bits. The default is returned
by running the command @samp{@gpgname --gpgconf-list}.
@item Subkey-Usage: @var{usage-list}
Key usage lists for a subkey; similar to @samp{Key-Usage}.
@item Passphrase: @var{string}
If you want to specify a passphrase for the secret key, enter it here.
Default is to use the Pinentry dialog to ask for a passphrase.
@item Name-Real: @var{name}
@itemx Name-Comment: @var{comment}
@itemx Name-Email: @var{email}
The three parts of a user name. Remember to use UTF-8 encoding here.
If you don't give any of them, no user ID is created.
@item Expire-Date: @var{iso-date}|(@var{number}[d|w|m|y])
Set the expiration date for the key (and the subkey). It may either
be entered in ISO date format (e.g. "20000815T145012") or as number of
days, weeks, month or years after the creation date. The special
notation "seconds=N" is also allowed to specify a number of seconds
since creation. Without a letter days are assumed. Note that there
is no check done on the overflow of the type used by OpenPGP for
timestamps. Thus you better make sure that the given value make
sense. Although OpenPGP works with time intervals, GnuPG uses an
absolute value internally and thus the last year we can represent is
2105.
@item Creation-Date: @var{iso-date}
Set the creation date of the key as stored in the key information and
which is also part of the fingerprint calculation. Either a date like
"1986-04-26" or a full timestamp like "19860426T042640" may be used.
The time is considered to be UTC. The special notation "seconds=N"
may be used to directly specify a the number of seconds since Epoch
(Unix time). If it is not given the current time is used.
@item Preferences: @var{string}
Set the cipher, hash, and compression preference values for this key.
This expects the same type of string as the sub-command @samp{setpref}
in the @option{--edit-key} menu.
@item Revoker: @var{algo}:@var{fpr} [sensitive]
Add a designated revoker to the generated key. Algo is the public key
algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
@var{fpr} is the fingerprint of the designated revoker. The optional
@samp{sensitive} flag marks the designated revoker as sensitive
information. Only v4 keys may be designated revokers.
@item Keyserver: @var{string}
This is an optional parameter that specifies the preferred keyserver
URL for the key.
@item Handle: @var{string}
This is an optional parameter only used with the status lines
KEY_CREATED and KEY_NOT_CREATED. @var{string} may be up to 100
characters and should not contain spaces. It is useful for batch key
generation to associate a key parameter block with a status line.
@end table
@noindent
Here is an example on how to create a key in an ephemeral home directory:
@smallexample
$ export GNUPGHOME="$(mktemp -d)"
$ cat >foo <<EOF
%echo Generating a basic OpenPGP key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
EOF
$ @gpgname --batch --generate-key foo
[...]
$ @gpgname --list-secret-keys
/tmp/tmp.0NQxB74PEf/pubring.kbx
-------------------------------
sec dsa1024 2016-12-16 [SCA]
768E895903FC1C44045C8CB95EEBDB71E9E849D0
uid [ultimate] Joe Tester (with stupid passphrase) <joe@@foo.bar>
ssb elg1024 2016-12-16 [E]
@end smallexample
@noindent
If you want to create a key with the default algorithms you would use
these parameters:
@smallexample
%echo Generating a default key
Key-Type: default
Subkey-Type: default
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
@end smallexample
@mansect see also
@ifset isman
@command{gpgv}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1)
@end ifset
@include see-also-note.texi
diff --git a/doc/tools.texi b/doc/tools.texi
index 51bfb09eb..985c0a75c 100644
--- a/doc/tools.texi
+++ b/doc/tools.texi
@@ -1,2190 +1,2190 @@
@c Copyright (C) 2004, 2008 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Helper Tools
@chapter Helper Tools
GnuPG comes with a couple of smaller tools:
@menu
* watchgnupg:: Read logs from a socket.
* gpgv:: Verify OpenPGP signatures.
* addgnupghome:: Create .gnupg home directories.
* gpgconf:: Modify .gnupg home directories.
* applygnupgdefaults:: Run gpgconf for all users.
* gpg-preset-passphrase:: Put a passphrase into the cache.
* gpg-connect-agent:: Communicate with a running agent.
* dirmngr-client:: How to use the Dirmngr client tool.
* gpgparsemail:: Parse a mail message into an annotated format
* symcryptrun:: Call a simple symmetric encryption tool.
* gpgtar:: Encrypt or sign files into an archive.
* gpg-check-pattern:: Check a passphrase on stdin against the patternfile.
@end menu
@c
@c WATCHGNUPG
@c
@manpage watchgnupg.1
@node watchgnupg
@section Read logs from a socket
@ifset manverb
.B watchgnupg
\- Read and print logs from a socket
@end ifset
@mansect synopsis
@ifset manverb
.B watchgnupg
.RB [ \-\-force ]
.RB [ \-\-verbose ]
.I socketname
@end ifset
@mansect description
Most of the main utilities are able to write their log files to a Unix
Domain socket if configured that way. @command{watchgnupg} is a simple
listener for such a socket. It ameliorates the output with a time stamp
and makes sure that long lines are not interspersed with log output from
other utilities. This tool is not available for Windows.
@noindent
@command{watchgnupg} is commonly invoked as
@example
watchgnupg
@end example
which is a shorthand for
@example
watchgnupg --force $(gpgconf --list-dirs socketdir)/S.log
@end example
To watch GnuPG running with a different home directory, use
@example
watchgnupg --homedir DIR
@end example
@manpause
@noindent
This starts it on the current terminal for listening on the standard
logging socket (this is commonly @file{/var/run/user/UID/gnupg/S.log}
or if no such user directory hierarchy exists @file{~/.gnupg/S.log}).
@mansect options
@noindent
@command{watchgnupg} understands these options:
@table @gnupgtabopt
@item --force
@opindex force
-Delete an already existing socket file. This option is implictly used
+Delete an already existing socket file. This option is implicitly used
if no socket name has been given on the command line.
@item --homedir @var{DIR}
If no socket name is given on the command line, pass @var{DIR} to
gpgconf so that the socket for a GnuPG running with DIR has its home
directory is used. Note that the environment variable @var{GNUPGHOME}
is ignored by watchgnupg.
@anchor{option watchgnupg --tcp}
@item --tcp @var{n}
Instead of reading from a local socket, listen for connects on TCP
port @var{n}. A Unix domain socket can optionally also be given as a
second source. This option does not use a default socket name.
@item --time-only
@opindex time-only
Do not print the date part of the timestamp.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@noindent
@mansect examples
@chapheading Examples
@example
$ watchgnupg --time-only
@end example
This waits for connections on the local socket
(e.g. @file{/var/run/user/1234/gnupg/S.log}) and shows all log
entries. To make this work the option @option{log-file} needs to be
used with all modules which logs are to be shown. The suggested entry
for the configuration files is:
@example
log-file socket://
@end example
If the default socket as given above and returned by "echo $(gpgconf
--list-dirs socketdir)/S.log" is not desired an arbitrary socket name
can be specified, for example @file{socket:///home/foo/bar/mysocket}.
For debugging purposes it is also possible to do remote logging. Take
care if you use this feature because the information is send in the
clear over the network. Use this syntax in the conf files:
@example
log-file tcp://192.168.1.1:4711
@end example
You may use any port and not just 4711 as shown above; only IP
addresses are supported (v4 and v6) and no host names. You need to
start @command{watchgnupg} with the @option{tcp} option. Note that
under Windows the registry entry
@var{HKCU\Software\GNU\GnuPG:DefaultLogFile} can be used to change the
default log output from @code{stderr} to whatever is given by that
entry. However the only useful entry is a TCP name for remote
debugging.
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPGV
@c
@include gpgv.texi
@c
@c ADDGNUPGHOME
@c
@manpage addgnupghome.8
@node addgnupghome
@section Create .gnupg home directories
@ifset manverb
.B addgnupghome
\- Create .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B addgnupghome
.I account_1
.IR account_2 ... account_n
@end ifset
@mansect description
If GnuPG is installed on a system with existing user accounts, it is
sometimes required to populate the GnuPG home directory with existing
files. Especially a @file{trustlist.txt} and a keybox with some
initial certificates are often desired. This script helps to do this
by copying all files from @file{/etc/skel/.gnupg} to the home
directories of the accounts given on the command line. It takes care
not to overwrite existing GnuPG home directories.
@noindent
@command{addgnupghome} is invoked by root as:
@example
addgnupghome account1 account2 ... accountn
@end example
@c
@c GPGCONF
@c
@manpage gpgconf.1
@node gpgconf
@section Modify .gnupg home directories
@ifset manverb
.B gpgconf
\- Modify .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B gpgconf
.RI [ options ]
.B \-\-list-components
.br
.B gpgconf
.RI [ options ]
.B \-\-list-options
.I component
.br
.B gpgconf
.RI [ options ]
.B \-\-change-options
.I component
@end ifset
@mansect description
The @command{gpgconf} is a utility to automatically and reasonable
safely query and modify configuration files in the @file{.gnupg} home
directory. It is designed not to be invoked manually by the user, but
automatically by graphical user interfaces (GUI).@footnote{Please note
that currently no locking is done, so concurrent access should be
avoided. There are some precautions to avoid corruption with
concurrent usage, but results may be inconsistent and some changes may
get lost. The stateless design makes it difficult to provide more
guarantees.}
@command{gpgconf} provides access to the configuration of one or more
components of the GnuPG system. These components correspond more or
less to the programs that exist in the GnuPG framework, like GPG,
GPGSM, DirMngr, etc. But this is not a strict one-to-one
relationship. Not all configuration options are available through
@command{gpgconf}. @command{gpgconf} provides a generic and abstract
method to access the most important configuration options that can
feasibly be controlled via such a mechanism.
@command{gpgconf} can be used to gather and change the options
available in each component, and can also provide their default
values. @command{gpgconf} will give detailed type information that
can be used to restrict the user's input without making an attempt to
commit the changes.
@command{gpgconf} provides the backend of a configuration editor. The
configuration editor would usually be a graphical user interface
program that displays the current options, their default
values, and allows the user to make changes to the options. These
changes can then be made active with @command{gpgconf} again. Such a
program that uses @command{gpgconf} in this way will be called GUI
throughout this section.
@menu
* Invoking gpgconf:: List of all commands and options.
* Format conventions:: Formatting conventions relevant for all commands.
* Listing components:: List all gpgconf components.
* Checking programs:: Check all programs known to gpgconf.
* Listing options:: List all options of a component.
* Changing options:: Changing options of a component.
* Listing global options:: List all global options.
* Querying versions:: Get and compare software versions.
* Files used by gpgconf:: What files are used by gpgconf.
@end menu
@manpause
@node Invoking gpgconf
@subsection Invoking gpgconf
@mansect commands
One of the following commands must be given:
@table @gnupgtabopt
@item --list-components
List all components. This is the default command used if none is
specified.
@item --check-programs
List all available backend programs and test whether they are runnable.
@item --list-options @var{component}
List all options of the component @var{component}.
@item --change-options @var{component}
Change the options of the component @var{component}.
@item --check-options @var{component}
Check the options for the component @var{component}.
@item --apply-profile @var{file}
Apply the configuration settings listed in @var{file} to the
configuration files. If @var{file} has no suffix and no slashes the
command first tries to read a file with the suffix @code{.prf} from
the data directory (@code{gpgconf --list-dirs datadir}) before it
reads the file verbatim. A profile is divided into sections using the
bracketed component name. Each section then lists the option which
shall go into the respective configuration file.
@item --apply-defaults
Update all configuration files with values taken from the global
configuration file (usually @file{/etc/gnupg/gpgconf.conf}).
@item --list-dirs [@var{names}]
Lists the directories used by @command{gpgconf}. One directory is
listed per line, and each line consists of a colon-separated list where
the first field names the directory type (for example @code{sysconfdir})
and the second field contains the percent-escaped directory. Although
they are not directories, the socket file names used by
@command{gpg-agent} and @command{dirmngr} are printed as well. Note
that the socket file names and the @code{homedir} lines are the default
names and they may be overridden by command line switches. If
@var{names} are given only the directories or file names specified by
the list names are printed without any escaping.
@item --list-config [@var{filename}]
List the global configuration file in a colon separated format. If
@var{filename} is given, check that file instead.
@item --check-config [@var{filename}]
Run a syntax check on the global configuration file. If @var{filename}
is given, check that file instead.
@item --query-swdb @var{package_name} [@var{version_string}]
Returns the current version for @var{package_name} and if
@var{version_string} is given also an indicator on whether an update
is available. The actual file with the software version is
automatically downloaded and checked by @command{dirmngr}.
@command{dirmngr} uses a thresholds to avoid download the file too
often and it does this by default only if it can be done via Tor. To
force an update of that file this command can be used:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --reload [@var{component}]
@opindex reload
Reload all or the given component. This is basically the same as
sending a SIGHUP to the component. Components which don't support
reloading are ignored. Without @var{component} or by using "all" for
@var{component} all components which are daemons are reloaded.
@item --launch [@var{component}]
@opindex launch
If the @var{component} is not already running, start it.
@command{component} must be a daemon. This is in general not required
because the system starts these daemons as needed. However, external
software making direct use of @command{gpg-agent} or @command{dirmngr}
may use this command to ensure that they are started. Using "all" for
@var{component} launches all components which are daemons.
@item --kill [@var{component}]
@opindex kill
Kill the given component that runs as a daemon, including
@command{gpg-agent}, @command{dirmngr}, and @command{scdaemon}. A
@command{component} which does not run as a daemon will be ignored.
Using "all" for @var{component} kills all components running as
daemons. Note that as of now reload and kill have the same effect for
@command{scdaemon}.
@item --create-socketdir
@opindex create-socketdir
Create a directory for sockets below /run/user or /var/run/user. This
is command is only required if a non default home directory is used
and the /run based sockets shall be used. For the default home
directory GnUPG creates a directory on the fly.
@item --remove-socketdir
@opindex remove-socketdir
Remove a directory created with command @option{--create-socketdir}.
@end table
@mansect options
The following options may be used:
@table @gnupgtabopt
@item -o @var{file}
@itemx --output @var{file}
Write output to @var{file}. Default is to write to stdout.
@item -v
@itemx --verbose
Outputs additional information while running. Specifically, this
extends numerical field values by human-readable descriptions.
@item -q
@itemx --quiet
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item -n
@itemx --dry-run
Do not actually change anything. This is currently only implemented
for @code{--change-options} and can be used for testing purposes.
@item -r
@itemx --runtime
Only used together with @code{--change-options}. If one of the
modified options can be changed in a running daemon process, signal
the running daemon to ask it to reparse its configuration file after
changing.
This means that the changes will take effect at run-time, as far as
this is possible. Otherwise, they will take effect at the next start
of the respective backend programs.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@manpause
@end table
@node Format conventions
@subsection Format conventions
Some lines in the output of @command{gpgconf} contain a list of
colon-separated fields. The following conventions apply:
@itemize @bullet
@item
The GUI program is required to strip off trailing newline and/or
carriage return characters from the output.
@item
@command{gpgconf} will never leave out fields. If a certain version
provides a certain field, this field will always be present in all
@command{gpgconf} versions from that time on.
@item
Future versions of @command{gpgconf} might append fields to the list.
New fields will always be separated from the previously last field by
a colon separator. The GUI should be prepared to parse the last field
it knows about up until a colon or end of line.
@item
Not all fields are defined under all conditions. You are required to
ignore the content of undefined fields.
@end itemize
There are several standard types for the content of a field:
@table @asis
@item verbatim
Some fields contain strings that are not escaped in any way. Such
fields are described to be used @emph{verbatim}. These fields will
never contain a colon character (for obvious reasons). No de-escaping
or other formatting is required to use the field content. This is for
easy parsing of the output, when it is known that the content can
never contain any special characters.
@item percent-escaped
Some fields contain strings that are described to be
@emph{percent-escaped}. Such strings need to be de-escaped before
their content can be presented to the user. A percent-escaped string
is de-escaped by replacing all occurrences of @code{%XY} by the byte
that has the hexadecimal value @code{XY}. @code{X} and @code{Y} are
from the set @code{0-9a-f}.
@item localized
Some fields contain strings that are described to be @emph{localized}.
Such strings are translated to the active language and formatted in
the active character set.
@item @w{unsigned number}
Some fields contain an @emph{unsigned number}. This number will
always fit into a 32-bit unsigned integer variable. The number may be
followed by a space, followed by a human readable description of that
value (if the verbose option is used). You should ignore everything
in the field that follows the number.
@item @w{signed number}
Some fields contain a @emph{signed number}. This number will always
fit into a 32-bit signed integer variable. The number may be followed
by a space, followed by a human readable description of that value (if
the verbose option is used). You should ignore everything in the
field that follows the number.
@item @w{boolean value}
Some fields contain a @emph{boolean value}. This is a number with
either the value 0 or 1. The number may be followed by a space,
followed by a human readable description of that value (if the verbose
option is used). You should ignore everything in the field that follows
the number; checking just the first character is sufficient in this
case.
@item option
Some fields contain an @emph{option} argument. The format of an
option argument depends on the type of the option and on some flags:
@table @asis
@item no argument
The simplest case is that the option does not take an argument at all
(@var{type} @code{0}). Then the option argument is an unsigned number
that specifies how often the option occurs. If the @code{list} flag
is not set, then the only valid number is @code{1}. Options that do
not take an argument never have the @code{default} or @code{optional
arg} flag set.
@item number
If the option takes a number argument (@var{alt-type} is @code{2} or
@code{3}), and it can only occur once (@code{list} flag is not set),
then the option argument is either empty (only allowed if the argument
is optional), or it is a number. A number is a string that begins
with an optional minus character, followed by one or more digits. The
number must fit into an integer variable (unsigned or signed,
depending on @var{alt-type}).
@item number list
If the option takes a number argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of numbers as described above.
@item string
If the option takes a string argument (@var{alt-type} is 1), and it
can only occur once (@code{list} flag is not set) then the option
argument is either empty (only allowed if the argument is optional),
or it starts with a double quote character (@code{"}) followed by a
percent-escaped string that is the argument value. Note that there is
only a leading double quote character, no trailing one. The double
quote character is only needed to be able to differentiate between no
value and the empty string as value.
@item string list
If the option takes a string argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of string arguments as described above.
@end table
@end table
The active language and character set are currently determined from
the locale environment of the @command{gpgconf} program.
@c FIXME: Document the active language and active character set. Allow
@c to change it via the command line?
@mansect usage
@node Listing components
@subsection Listing components
The command @code{--list-components} will list all components that can
be configured with @command{gpgconf}. Usually, one component will
correspond to one GnuPG-related program and contain the options of
that program's configuration file that can be modified using
@command{gpgconf}. However, this is not necessarily the case. A
component might also be a group of selected options from several
programs, or contain entirely virtual options that have a special
effect rather than changing exactly one option in one configuration
file.
A component is a set of configuration options that semantically belong
together. Furthermore, several changes to a component can be made in
an atomic way with a single operation. The GUI could for example
provide a menu with one entry for each component, or a window with one
tabulator sheet per component.
The command @code{--list-components} lists all available
components, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:}
@table @var
@item name
This field contains a name tag of the component. The name tag is used
to specify the component in all communication with @command{gpgconf}.
The name tag is to be used @emph{verbatim}. It is thus not in any
escaped format.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@end table
Example:
@example
$ gpgconf --list-components
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:
@end example
@node Checking programs
@subsection Checking programs
The command @code{--check-programs} is similar to
@code{--list-components} but works on backend programs and not on
components. It runs each program to test whether it is installed and
runnable. This also includes a syntax check of all config file options
of the program.
The command @code{--check-programs} lists all available
programs, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:@var{avail}:@var{okay}:@var{cfgfile}:@var{line}:@var{error}:}
@table @var
@item name
This field contains a name tag of the program which is identical to the
name of the component. The name tag is to be used @emph{verbatim}. It
is thus not in any escaped format. This field may be empty to indicate
a continuation of error descriptions for the last name. The description
and pgmname fields are then also empty.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@item avail
The @emph{boolean value} in this field indicates whether the program is
installed and runnable.
@item okay
The @emph{boolean value} in this field indicates whether the program's
config file is syntactically okay.
@item cfgfile
If an error occurred in the configuration file (as indicated by a false
value in the field @code{okay}), this field has the name of the failing
configuration file. It is @emph{percent-escaped}.
@item line
If an error occurred in the configuration file, this field has the line
number of the failing statement in the configuration file.
It is an @emph{unsigned number}.
@item error
If an error occurred in the configuration file, this field has the error
text of the failing statement in the configuration file. It is
@emph{percent-escaped} and @emph{localized}.
@end table
@noindent
In the following example the @command{dirmngr} is not runnable and the
configuration file of @command{scdaemon} is not okay.
@example
$ gpgconf --check-programs
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:1:1:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:1:1:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:1:0:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:1:1:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:0:0:
@end example
@noindent
The command @w{@code{--check-options @var{component}}} will verify the
configuration file in the same manner as @code{--check-programs}, but
only for the component @var{component}.
@node Listing options
@subsection Listing options
Every component contains one or more options. Options may be gathered
into option groups to allow the GUI to give visual hints to the user
about which options are related.
The command @code{@w{--list-options @var{component}}} lists
all options (and the groups they belong to) in the component
@var{component}, one per line. @var{component} must be the string in
the field @var{name} in the output of the @code{--list-components}
command.
There is one line for each option and each group. First come all
options that are not in any group. Then comes a line describing a
group. Then come all options that belong into each group. Then comes
the next group and so on. There does not need to be any group (and in
this case the output will stop after the last non-grouped option).
The format of each line is:
@code{@var{name}:@var{flags}:@var{level}:@var{description}:@var{type}:@var{alt-type}:@var{argname}:@var{default}:@var{argdef}:@var{value}}
@table @var
@item name
This field contains a name tag for the group or option. The name tag
is used to specify the group or option in all communication with
@command{gpgconf}. The name tag is to be used @emph{verbatim}. It is
thus not in any escaped format.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item group (1)
If this flag is set, this is a line describing a group and not an
option.
@end table
The following flag values are only defined for options (that is, if
the @code{group} flag is not used).
@table @code
@item optional arg (2)
If this flag is set, the argument is optional. This is never set for
@var{type} @code{0} (none) options.
@item list (4)
If this flag is set, the option can be given multiple times.
@item runtime (8)
If this flag is set, the option can be changed at runtime.
@item default (16)
If this flag is set, a default value is available.
@item default desc (32)
If this flag is set, a (runtime) default is available. This and the
@code{default} flag are mutually exclusive.
@item no arg desc (64)
If this flag is set, and the @code{optional arg} flag is set, then the
option has a special meaning if no argument is given.
@item no change (128)
If this flag is set, @command{gpgconf} ignores requests to change the
value. GUI frontends should grey out this option. Note, that manual
changes of the configuration files are still possible.
@end table
@item level
This field is defined for options and for groups. It contains an
@emph{unsigned number} that specifies the expert level under which
this group or option should be displayed. The following expert levels
are defined for options (they have analogous meaning for groups):
@table @code
@item basic (0)
This option should always be offered to the user.
@item advanced (1)
This option may be offered to advanced users.
@item expert (2)
This option should only be offered to expert users.
@item invisible (3)
This option should normally never be displayed, not even to expert
users.
@item internal (4)
This option is for internal use only. Ignore it.
@end table
The level of a group will always be the lowest level of all options it
contains.
@item description
This field is defined for options and groups. The @emph{string} in
this field contains a human-readable description of the option or
group. It can be displayed to the user of the GUI for informational
purposes. It is @emph{percent-escaped} and @emph{localized}.
@item type
This field is only defined for options. It contains an @emph{unsigned
number} that specifies the type of the option's argument, if any. The
following types are defined:
Basic types:
@table @code
@item none (0)
No argument allowed.
@item string (1)
An @emph{unformatted string}.
@item int32 (2)
A @emph{signed number}.
@item uint32 (3)
An @emph{unsigned number}.
@end table
Complex types:
@table @code
@item pathname (32)
A @emph{string} that describes the pathname of a file. The file does
not necessarily need to exist.
@item ldap server (33)
A @emph{string} that describes an LDAP server in the format:
@code{@var{hostname}:@var{port}:@var{username}:@var{password}:@var{base_dn}}
@item key fingerprint (34)
A @emph{string} with a 40 digit fingerprint specifying a certificate.
@item pub key (35)
A @emph{string} that describes a certificate by user ID, key ID or
fingerprint.
@item sec key (36)
A @emph{string} that describes a certificate with a key by user ID,
key ID or fingerprint.
@item alias list (37)
A @emph{string} that describes an alias list, like the one used with
gpg's group option. The list consists of a key, an equal sign and space
separated values.
@end table
More types will be added in the future. Please see the @var{alt-type}
field for information on how to cope with unknown types.
@item alt-type
This field is identical to @var{type}, except that only the types
@code{0} to @code{31} are allowed. The GUI is expected to present the
user the option in the format specified by @var{type}. But if the
argument type @var{type} is not supported by the GUI, it can still
display the option in the more generic basic type @var{alt-type}. The
GUI must support all the defined basic types to be able to display all
options. More basic types may be added in future versions. If the
GUI encounters a basic type it doesn't support, it should report an
error and abort the operation.
@item argname
This field is only defined for options with an argument type
@var{type} that is not @code{0}. In this case it may contain a
@emph{percent-escaped} and @emph{localized string} that gives a short
name for the argument. The field may also be empty, though, in which
case a short name is not known.
@item default
This field is defined only for options for which the @code{default} or
@code{default desc} flag is set. If the @code{default} flag is set,
its format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default value
for this option. If the @code{default desc} flag is set, the field is
either empty or contains a description of the effect if the option is
not given.
@item argdef
This field is defined only for options for which the @code{optional
arg} flag is set. If the @code{no arg desc} flag is not set, its
format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default argument
for this option. If the @code{no arg desc} flag is set, the field is
either empty or contains a description of the effect of this option if
no argument is given.
@item value
This field is defined only for options. Its format is that of an
@emph{option argument}. If it is empty, then the option is not
explicitly set in the current configuration, and the default applies
(if any). Otherwise, it contains the current value of the option.
Note that this field is also meaningful if the option itself does not
take a real argument (in this case, it contains the number of times
the option appears).
@end table
@node Changing options
@subsection Changing options
The command @w{@code{--change-options @var{component}}} will attempt
to change the options of the component @var{component} to the
specified values. @var{component} must be the string in the field
@var{name} in the output of the @code{--list-components} command. You
have to provide the options that shall be changed in the following
format on standard input:
@code{@var{name}:@var{flags}:@var{new-value}}
@table @var
@item name
This is the name of the option to change. @var{name} must be the
string in the field @var{name} in the output of the
@code{--list-options} command.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item default (16)
If this flag is set, the option is deleted and the default value is
used instead (if applicable).
@end table
@item new-value
The new value for the option. This field is only defined if the
@code{default} flag is not set. The format is that of an @emph{option
argument}. If it is empty (or the field is omitted), the default
argument is used (only allowed if the argument is optional for this
option). Otherwise, the option will be set to the specified value.
@end table
@noindent
The output of the command is the same as that of
@code{--check-options} for the modified configuration file.
Examples:
To set the force option, which is of basic type @code{none (0)}:
@example
$ echo 'force:0:1' | gpgconf --change-options dirmngr
@end example
To delete the force option:
@example
$ echo 'force:16:' | gpgconf --change-options dirmngr
@end example
The @code{--runtime} option can influence when the changes take
effect.
@node Listing global options
@subsection Listing global options
Sometimes it is useful for applications to look at the global options
file @file{gpgconf.conf}.
The colon separated listing format is record oriented and uses the first
field to identify the record type:
@table @code
@item k
This describes a key record to start the definition of a new ruleset for
a user/group. The format of a key record is:
@code{k:@var{user}:@var{group}:}
@table @var
@item user
This is the user field of the key. It is percent escaped. See the
definition of the gpgconf.conf format for details.
@item group
This is the group field of the key. It is percent escaped.
@end table
@item r
This describes a rule record. All rule records up to the next key record
make up a rule set for that key. The format of a rule record is:
@code{r:::@var{component}:@var{option}:@var{flag}:@var{value}:}
@table @var
@item component
This is the component part of a rule. It is a plain string.
@item option
This is the option part of a rule. It is a plain string.
@item flag
This is the flags part of a rule. There may be only one flag per rule
but by using the same component and option, several flags may be
assigned to an option. It is a plain string.
@item value
This is the optional value for the option. It is a percent escaped
string with a single quotation mark to indicate a string. The quotation
mark is only required to distinguish between no value specified and an
empty string.
@end table
@end table
@noindent
Unknown record types should be ignored. Note that there is intentionally
no feature to change the global option file through @command{gpgconf}.
@node Querying versions
@subsection Get and compare software versions.
The GnuPG Project operates a server to query the current versions of
software packages related to GnuPG. @command{gpgconf} can be used to
access this online database. To allow for offline operations, this
feature works by having @command{dirmngr} download a file from
@code{https://versions.gnupg.org}, checking the signature of that file
and storing the file in the GnuPG home directory. If
@command{gpgconf} is used and @command{dirmngr} is running, it may ask
@command{dirmngr} to refresh that file before itself uses the file.
The command @option{--query-swdb} returns information for the given
package in a colon delimited format:
@table @var
@item name
This is the name of the package as requested. Note that "gnupg" is a
special name which is replaced by the actual package implementing this
version of GnuPG. For this name it is also not required to specify a
version because @command{gpgconf} takes its own version in this case.
@item iversion
The currently installed version or an empty string. The value is
taken from the command line argument but may be provided by gpg
if not given.
@item status
The status of the software package according to this table:
@table @code
@item -
No information available. This is either because no current version
has been specified or due to an error.
@item ?
The given name is not known in the online database.
@item u
An update of the software is available.
@item c
The installed version of the software is current.
@item n
The installed version is already newer than the released version.
@end table
@item urgency
If the value (the empty string should be considered as zero) is
greater than zero an important update is available.
@item error
This returns an @command{gpg-error} error code to distinguish between
various failure modes.
@item filedate
This gives the date of the file with the version numbers in standard
ISO format (@code{yyyymmddThhmmss}). The date has been extracted by
@command{dirmngr} from the signature of the file.
@item verified
This gives the date in ISO format the file was downloaded. This value
can be used to evaluate the freshness of the information.
@item version
This returns the version string for the requested software from the
file.
@item reldate
This returns the release date in ISO format.
@item size
This returns the size of the package as decimal number of bytes.
@item hash
This returns a hexified SHA-2 hash of the package.
@end table
@noindent
More fields may be added in future to the output.
@mansect files
@node Files used by gpgconf
@subsection Files used by gpgconf
@table @file
@item /etc/gnupg/gpgconf.conf
@cindex gpgconf.conf
If this file exists, it is processed as a global configuration file.
A commented example can be found in the @file{examples} directory of
the distribution.
@item @var{GNUPGHOME}/swdb.lst
@cindex swdb.lst
A file with current software versions. @command{dirmngr} creates
this file on demand from an online resource.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1),
@command{dirmngr}(1)
@end ifset
@include see-also-note.texi
@c
@c APPLYGNUPGDEFAULTS
@c
@manpage applygnupgdefaults.8
@node applygnupgdefaults
@section Run gpgconf for all users
@ifset manverb
.B applygnupgdefaults
\- Run gpgconf --apply-defaults for all users.
@end ifset
@mansect synopsis
@ifset manverb
.B applygnupgdefaults
@end ifset
@mansect description
This script is a wrapper around @command{gpgconf} to run it with the
command @code{--apply-defaults} for all real users with an existing
GnuPG home directory. Admins might want to use this script to update he
GnuPG configuration files for all users after
@file{/etc/gnupg/gpgconf.conf} has been changed. This allows enforcing
certain policies for all users. Note, that this is not a bulletproof way to
force a user to use certain options. A user may always directly edit
the configuration files and bypass gpgconf.
@noindent
@command{applygnupgdefaults} is invoked by root as:
@example
applygnupgdefaults
@end example
@c
@c GPG-PRESET-PASSPHRASE
@c
@node gpg-preset-passphrase
@section Put a passphrase into the cache
@manpage gpg-preset-passphrase.1
@ifset manverb
.B gpg-preset-passphrase
\- Put a passphrase into gpg-agent's cache
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-preset-passphrase
.RI [ options ]
.RI [ command ]
.I cache-id
@end ifset
@mansect description
The @command{gpg-preset-passphrase} is a utility to seed the internal
cache of a running @command{gpg-agent} with passphrases. It is mainly
useful for unattended machines, where the usual @command{pinentry} tool
may not be used and the passphrases for the to be used keys are given at
machine startup.
This program works with GnuPG 2 and later. GnuPG 1.x is not supported.
Passphrases set with this utility don't expire unless the
@option{--forget} option is used to explicitly clear them from the
cache --- or @command{gpg-agent} is either restarted or reloaded (by
sending a SIGHUP to it). Note that the maximum cache time as set with
@option{--max-cache-ttl} is still honored. It is necessary to allow
this passphrase presetting by starting @command{gpg-agent} with the
@option{--allow-preset-passphrase}.
@menu
* Invoking gpg-preset-passphrase:: List of all commands and options.
@end menu
@manpause
@node Invoking gpg-preset-passphrase
@subsection List of all commands and options
@mancont
@noindent
@command{gpg-preset-passphrase} is invoked this way:
@example
gpg-preset-passphrase [options] [command] @var{cacheid}
@end example
@var{cacheid} is either a 40 character keygrip of hexadecimal
characters identifying the key for which the passphrase should be set
or cleared. The keygrip is listed along with the key when running the
command: @code{gpgsm --with-keygrip --list-secret-keys}.
Alternatively an arbitrary string may be used to identify a
passphrase; it is suggested that such a string is prefixed with the
name of the application (e.g @code{foo:12346}). Scripts should always
use the option @option{--with-colons}, which provides the keygrip in a
"grp" line (cf. @file{doc/DETAILS})/
@noindent
One of the following command options must be given:
@table @gnupgtabopt
@item --preset
@opindex preset
Preset a passphrase. This is what you usually will
use. @command{gpg-preset-passphrase} will then read the passphrase from
@code{stdin}.
@item --forget
@opindex forget
Flush the passphrase for the given cache ID from the cache.
@end table
@noindent
The following additional options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -P @var{string}
@itemx --passphrase @var{string}
@opindex passphrase
Instead of reading the passphrase from @code{stdin}, use the supplied
@var{string} as passphrase. Note that this makes the passphrase visible
for other users.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPG-CONNECT-AGENT
@c
@node gpg-connect-agent
@section Communicate with a running agent
@manpage gpg-connect-agent.1
@ifset manverb
.B gpg-connect-agent
\- Communicate with a running agent
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-connect-agent
.RI [ options ] [commands]
@end ifset
@mansect description
The @command{gpg-connect-agent} is a utility to communicate with a
running @command{gpg-agent}. It is useful to check out the commands
@command{gpg-agent} provides using the Assuan interface. It might
also be useful for scripting simple applications. Input is expected
at stdin and output gets printed to stdout.
It is very similar to running @command{gpg-agent} in server mode; but
here we connect to a running instance.
@menu
* Invoking gpg-connect-agent:: List of all options.
* Controlling gpg-connect-agent:: Control commands.
@end menu
@manpause
@node Invoking gpg-connect-agent
@subsection List of all options
@noindent
@command{gpg-connect-agent} is invoked this way:
@example
gpg-connect-agent [options] [commands]
@end example
@mancont
@noindent
The following options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -q
@item --quiet
@opindex q
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify the directory manager (keyserver client) program to be started
if none is running. This has only an effect if used together with the
option @option{--dirmngr}.
@item --keyboxd-program @var{file}
@opindex keyboxd-program
Specify the keybox daemon program to be started if none is running.
This has only an effect if used together with the option
@option{--keyboxd}.
@item --dirmngr
@opindex dirmngr
Connect to a running directory manager (keyserver client) instead of
to the gpg-agent. If a dirmngr is not running, start it.
@item --keyboxd
@opindex keyboxd
Connect to a running keybox daemon instead of
to the gpg-agent. If a keyboxd is not running, start it.
@item -S
@itemx --raw-socket @var{name}
@opindex raw-socket
Connect to socket @var{name} assuming this is an Assuan style server.
Do not run any special initializations or environment checks. This may
be used to directly connect to any Assuan style socket server.
@item -E
@itemx --exec
@opindex exec
Take the rest of the command line as a program and it's arguments and
execute it as an Assuan server. Here is how you would run @command{gpgsm}:
@smallexample
gpg-connect-agent --exec gpgsm --server
@end smallexample
Note that you may not use options on the command line in this case.
@item --no-ext-connect
@opindex no-ext-connect
When using @option{-S} or @option{--exec}, @command{gpg-connect-agent}
connects to the Assuan server in extended mode to allow descriptor
passing. This option makes it use the old mode.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started.
@item -r @var{file}
@itemx --run @var{file}
@opindex run
Run the commands from @var{file} at startup and then continue with the
regular input method. Note, that commands given on the command line are
executed after this file.
@item -s
@itemx --subst
@opindex subst
Run the command @code{/subst} at startup.
@item --hex
@opindex hex
Print data lines in a hex format and the ASCII representation of
non-control characters.
@item --decode
@opindex decode
Decode data lines. That is to remove percent escapes but make sure that
a new line always starts with a D and a space.
@end table
@mansect control commands
@node Controlling gpg-connect-agent
@subsection Control commands
While reading Assuan commands, gpg-agent also allows a few special
commands to control its operation. These control commands all start
with a slash (@code{/}).
@table @code
@item /echo @var{args}
Just print @var{args}.
@item /let @var{name} @var{value}
Set the variable @var{name} to @var{value}. Variables are only
substituted on the input if the @command{/subst} has been used.
Variables are referenced by prefixing the name with a dollar sign and
optionally include the name in curly braces. The rules for a valid name
are identically to those of the standard bourne shell. This is not yet
enforced but may be in the future. When used with curly braces no
leading or trailing white space is allowed.
If a variable is not found, it is searched in the environment and if
found copied to the table of variables.
Variable functions are available: The name of the function must be
followed by at least one space and the at least one argument. The
following functions are available:
@table @code
@item get
Return a value described by the argument. Available arguments are:
@table @code
@item cwd
The current working directory.
@item homedir
The gnupg homedir.
@item sysconfdir
GnuPG's system configuration directory.
@item bindir
GnuPG's binary directory.
@item libdir
GnuPG's library directory.
@item libexecdir
GnuPG's library directory for executable files.
@item datadir
GnuPG's data directory.
@item serverpid
The PID of the current server. Command @command{/serverpid} must
have been given to return a useful value.
@end table
@item unescape @var{args}
Remove C-style escapes from @var{args}. Note that @code{\0} and
@code{\x00} terminate the returned string implicitly. The string to be
converted are the entire arguments right behind the delimiting space of
the function name.
@item unpercent @var{args}
@itemx unpercent+ @var{args}
Remove percent style escaping from @var{args}. Note that @code{%00}
terminates the string implicitly. The string to be converted are the
entire arguments right behind the delimiting space of the function
name. @code{unpercent+} also maps plus signs to a spaces.
@item percent @var{args}
@itemx percent+ @var{args}
Escape the @var{args} using percent style escaping. Tabs, formfeeds,
linefeeds, carriage returns and colons are escaped. @code{percent+} also
maps spaces to plus signs.
@item errcode @var{arg}
@itemx errsource @var{arg}
@itemx errstring @var{arg}
Assume @var{arg} is an integer and evaluate it using @code{strtol}. Return
the gpg-error error code, error source or a formatted string with the
error code and error source.
@item +
@itemx -
@itemx *
@itemx /
@itemx %
Evaluate all arguments as long integers using @code{strtol} and apply
this operator. A division by zero yields an empty string.
@item !
@itemx |
@itemx &
Evaluate all arguments as long integers using @code{strtol} and apply
the logical operators NOT, OR or AND. The NOT operator works on the
last argument only.
@end table
@item /definq @var{name} @var{var}
Use content of the variable @var{var} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqfile @var{name} @var{file}
Use content of @var{file} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqprog @var{name} @var{prog}
Run @var{prog} for inquiries matching @var{name} and pass the
entire line to it as command line arguments.
@item /datafile @var{name}
Write all data lines from the server to the file @var{name}. The file
is opened for writing and created if it does not exists. An existing
file is first truncated to 0. The data written to the file fully
decoded. Using a single dash for @var{name} writes to stdout. The
file is kept open until a new file is set using this command or this
command is used without an argument.
@item /showdef
Print all definitions
@item /cleardef
Delete all definitions
@item /sendfd @var{file} @var{mode}
Open @var{file} in @var{mode} (which needs to be a valid @code{fopen}
mode string) and send the file descriptor to the server. This is
usually followed by a command like @code{INPUT FD} to set the
input source for other commands.
@item /recvfd
Not yet implemented.
@item /open @var{var} @var{file} [@var{mode}]
Open @var{file} and assign the file descriptor to @var{var}. Warning:
This command is experimental and might change in future versions.
@item /close @var{fd}
Close the file descriptor @var{fd}. Warning: This command is
experimental and might change in future versions.
@item /showopen
Show a list of open files.
@item /serverpid
Send the Assuan command @command{GETINFO pid} to the server and store
the returned PID for internal purposes.
@item /sleep
Sleep for a second.
@item /hex
@itemx /nohex
Same as the command line option @option{--hex}.
@item /decode
@itemx /nodecode
Same as the command line option @option{--decode}.
@item /subst
@itemx /nosubst
Enable and disable variable substitution. It defaults to disabled
unless the command line option @option{--subst} has been used.
If /subst as been enabled once, leading whitespace is removed from
input lines which makes scripts easier to read.
@item /while @var{condition}
@itemx /end
These commands provide a way for executing loops. All lines between
the @code{while} and the corresponding @code{end} are executed as long
as the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function. Example:
@smallexample
/subst
/let i 3
/while $i
/echo loop counter is $i
/let i $@{- $i 1@}
/end
@end smallexample
@item /if @var{condition}
@itemx /end
These commands provide a way for conditional execution. All lines between
the @code{if} and the corresponding @code{end} are executed only if
the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function.
@item /run @var{file}
Run commands from @var{file}.
@item /bye
Terminate the connection and the program.
@item /help
Print a list of available control commands.
@end table
@ifset isman
@mansect see also
@command{gpg-agent}(1),
@command{scdaemon}(1)
@include see-also-note.texi
@end ifset
@c
@c DIRMNGR-CLIENT
@c
@node dirmngr-client
@section The Dirmngr Client Tool
@manpage dirmngr-client.1
@ifset manverb
.B dirmngr-client
\- Tool to access the Dirmngr services
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr-client
.RI [ options ]
.RI [ certfile | pattern ]
@end ifset
@mansect description
The @command{dirmngr-client} is a simple tool to contact a running
dirmngr and test whether a certificate has been revoked --- either by
being listed in the corresponding CRL or by running the OCSP protocol.
If no dirmngr is running, a new instances will be started but this is
in general not a good idea due to the huge performance overhead.
@noindent
The usual way to run this tool is either:
@example
dirmngr-client @var{acert}
@end example
@noindent
or
@example
dirmngr-client <@var{acert}
@end example
Where @var{acert} is one DER encoded (binary) X.509 certificates to be
tested.
@ifclear isman
The return value of this command is
@end ifclear
@mansect return value
@ifset isman
@command{dirmngr-client} returns these values:
@end ifset
@table @code
@item 0
The certificate under question is valid; i.e. there is a valid CRL
available and it is not listed there or the OCSP request returned that
that certificate is valid.
@item 1
The certificate has been revoked
@item 2 (and other values)
There was a problem checking the revocation state of the certificate.
A message to stderr has given more detailed information. Most likely
this is due to a missing or expired CRL or due to a network problem.
@end table
@mansect options
@noindent
@command{dirmngr-client} may be called with the following options:
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --quiet, -q
@opindex quiet
Make the output extra brief by suppressing any informational messages.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @samp{-vv}.
@item --pem
@opindex pem
Assume that the given certificate is in PEM (armored) format.
@item --ocsp
@opindex ocsp
Do the check using the OCSP protocol and ignore any CRLs.
@item --force-default-responder
@opindex force-default-responder
When checking using the OCSP protocol, force the use of the default OCSP
responder. That is not to use the Reponder as given by the certificate.
@item --ping
@opindex ping
Check whether the dirmngr daemon is up and running.
@item --cache-cert
@opindex cache-cert
Put the given certificate into the cache of a running dirmngr. This is
mainly useful for debugging.
@item --validate
@opindex validate
Validate the given certificate using dirmngr's internal validation code.
This is mainly useful for debugging.
@item --load-crl
@opindex load-crl
This command expects a list of filenames with DER encoded CRL files.
With the option @option{--url} URLs are expected in place of filenames
and they are loaded directly from the given location. All CRLs will be
validated and then loaded into dirmngr's cache.
@item --lookup
@opindex lookup
Take the remaining arguments and run a lookup command on each of them.
The results are Base-64 encoded outputs (without header lines). This
may be used to retrieve certificates from a server. However the output
format is not very well suited if more than one certificate is returned.
@item --url
@itemx -u
@opindex url
Modify the @command{lookup} and @command{load-crl} commands to take an URL.
@item --local
@itemx -l
@opindex url
Let the @command{lookup} command only search the local cache.
@item --squid-mode
@opindex squid-mode
Run @sc{dirmngr-client} in a mode suitable as a helper program for
Squid's @option{external_acl_type} option.
@end table
@ifset isman
@mansect see also
@command{dirmngr}(8),
@command{gpgsm}(1)
@include see-also-note.texi
@end ifset
@c
@c GPGPARSEMAIL
@c
@node gpgparsemail
@section Parse a mail message into an annotated format
@manpage gpgparsemail.1
@ifset manverb
.B gpgparsemail
\- Parse a mail message into an annotated format
@end ifset
@mansect synopsis
@ifset manverb
.B gpgparsemail
.RI [ options ]
.RI [ file ]
@end ifset
@mansect description
The @command{gpgparsemail} is a utility currently only useful for
debugging. Run it with @code{--help} for usage information.
@c
@c SYMCRYPTRUN
@c
@node symcryptrun
@section Call a simple symmetric encryption tool
@manpage symcryptrun.1
@ifset manverb
.B symcryptrun
\- Call a simple symmetric encryption tool
@end ifset
@mansect synopsis
@ifset manverb
.B symcryptrun
.B \-\-class
.I class
.B \-\-program
.I program
.B \-\-keyfile
.I keyfile
.RB [ --decrypt | --encrypt ]
.RI [ inputfile ]
@end ifset
@mansect description
Sometimes simple encryption tools are already in use for a long time
and there might be a desire to integrate them into the GnuPG
framework. The protocols and encryption methods might be non-standard
or not even properly documented, so that a full-fledged encryption
tool with an interface like @command{gpg} is not doable.
@command{symcryptrun} provides a solution: It operates by calling the
external encryption/decryption module and provides a passphrase for a
key using the standard @command{pinentry} based mechanism through
@command{gpg-agent}.
Note, that @command{symcryptrun} is only available if GnuPG has been
configured with @samp{--enable-symcryptrun} at build time.
@menu
* Invoking symcryptrun:: List of all commands and options.
@end menu
@manpause
@node Invoking symcryptrun
@subsection List of all commands and options
@noindent
@command{symcryptrun} is invoked this way:
@example
symcryptrun --class CLASS --program PROGRAM --keyfile KEYFILE
[--decrypt | --encrypt] [inputfile]
@end example
@mancont
For encryption, the plain text must be provided on STDIN or as the
argument @var{inputfile}, and the ciphertext will be output to STDOUT.
For decryption vice versa.
@var{CLASS} describes the calling conventions of the external tool.
Currently it must be given as @samp{confucius}. @var{PROGRAM} is
the full filename of that external tool.
For the class @samp{confucius} the option @option{--keyfile} is
required; @var{keyfile} is the name of a file containing the secret key,
which may be protected by a passphrase. For detailed calling
conventions, see the source code.
@noindent
Note, that @command{gpg-agent} must be running before starting
@command{symcryptrun}.
@noindent
The following additional options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -q
@item --quiet
@opindex q
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. Use @file{socket://} to log
to socket. Default is to write logging information to STDERR.
@end table
@noindent
The possible exit status codes of @command{symcryptrun} are:
@table @code
@item 0
Success.
@item 1
Some error occurred.
@item 2
No valid passphrase was provided.
@item 3
The operation was canceled by the user.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@end ifset
@include see-also-note.texi
@c
@c GPGTAR
@c
@manpage gpgtar.1
@node gpgtar
@section Encrypt or sign files into an archive
@ifset manverb
.B gpgtar
\- Encrypt or sign files into an archive
@end ifset
@mansect synopsis
@ifset manverb
.B gpgtar
.RI [ options ]
.I filename1
.I [ filename2, ... ]
.I directory1
.I [ directory2, ... ]
@end ifset
@mansect description
@command{gpgtar} encrypts or signs files into an archive. It is an
gpg-ized tar using the same format as used by PGP's PGP Zip.
@manpause
@noindent
@command{gpgtar} is invoked this way:
@example
gpgtar [options] @var{filename1} [@var{filename2}, ...] @var{directory} [@var{directory2}, ...]
@end example
@mansect options
@noindent
@command{gpgtar} understands these options:
@table @gnupgtabopt
@item --create
@opindex create
Put given files and directories into a vanilla ``ustar'' archive.
@item --extract
@opindex extract
Extract all files from a vanilla ``ustar'' archive.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt given files and directories into an archive. This option may
be combined with option @option{--symmetric} for an archive that may
be decrypted via a secret key or a passphrase.
@item --decrypt
@itemx -d
@opindex decrypt
Extract all files from an encrypted archive.
@item --sign
@itemx -s
Make a signed archive from the given files and directories. This can
be combined with option @option{--encrypt} to create a signed and then
encrypted archive.
@item --list-archive
@itemx -t
@opindex list-archive
List the contents of the specified archive.
@item --symmetric
@itemx -c
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option to @command{gpg}.
@item --recipient @var{user}
@itemx -r @var{user}
@opindex recipient
Encrypt for user id @var{user}. For details see @command{gpg}.
@item --local-user @var{user}
@itemx -u @var{user}
@opindex local-user
Use @var{user} as the key to sign with. For details see @command{gpg}.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write the archive to the specified file @var{file}.
@item --verbose
@itemx -v
@opindex verbose
Enable extra informational output.
@item --quiet
@itemx -q
@opindex quiet
Try to be as quiet as possible.
@item --skip-crypto
@opindex skip-crypto
Skip all crypto operations and create or extract vanilla ``ustar''
archives.
@item --dry-run
@opindex dry-run
Do not actually output the extracted files.
@item --directory @var{dir}
@itemx -C @var{dir}
@opindex directory
Extract the files into the directory @var{dir}. The default is to
take the directory name from the input filename. If no input filename
is known a directory named @file{GPGARCH} is used. For tarball
creation, switch to directory @var{dir} before performing any
operations.
@item --files-from @var{file}
@itemx -T @var{file}
Take the file names to work from the file @var{file}; one file per
line.
@item --null
@opindex null
Modify option @option{--files-from} to use a binary nul instead of a
linefeed to separate file names.
@item --openpgp
@opindex openpgp
This option has no effect because OpenPGP encryption and signing is
the default.
@item --cms
@opindex cms
This option is reserved and shall not be used. It will eventually be
used to encrypt or sign using the CMS protocol; but that is not yet
implemented.
@item --set-filename @var{file}
@opindex set-filename
Use the last component of @var{file} as the output directory. The
default is to take the directory name from the input filename. If no
input filename is known a directory named @file{GPGARCH} is used.
This option is deprecated in favor of option @option{--directory}.
@item --gpg @var{gpgcmd}
@opindex gpg
Use the specified command @var{gpgcmd} instead of @command{gpg}.
@item --gpg-args @var{args}
@opindex gpg-args
Pass the specified extra options to @command{gpg}.
@item --tar-args @var{args}
@opindex tar-args
Assume @var{args} are standard options of the command @command{tar}
and parse them. The only supported tar options are "--directory",
"--files-from", and "--null" This is an obsolete options because those
supported tar options can also be given directly.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@mansect diagnostics
@noindent
The program returns 0 if everything was fine, 1 otherwise.
@mansect examples
@ifclear isman
@noindent
Some examples:
@end ifclear
@noindent
Encrypt the contents of directory @file{mydocs} for user Bob to file
@file{test1}:
@example
gpgtar --encrypt --output test1 -r Bob mydocs
@end example
@noindent
List the contents of archive @file{test1}:
@example
gpgtar --list-archive test1
@end example
@mansect see also
@ifset isman
@command{gpg}(1),
@command{tar}(1),
@end ifset
@include see-also-note.texi
@c
@c GPG-CHECK-PATTERN
@c
@manpage gpg-check-pattern.1
@node gpg-check-pattern
@section Check a passphrase on stdin against the patternfile
@ifset manverb
.B gpg-check-pattern
\- Check a passphrase on stdin against the patternfile
@end ifset
@mansect synopsis
@ifset manverb
.B gpg\-check\-pattern
.RI [ options ]
.I patternfile
@end ifset
@mansect description
@command{gpg-check-pattern} checks a passphrase given on stdin against
a specified pattern file.
@mansect options
@noindent
@table @gnupgtabopt
@item --verbose
@opindex verbose
Enable extra informational output.
@item --check
@opindex check
Run only a syntax check on the patternfile.
@item --null
@opindex null
Input is expected to be null delimited.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@end ifset
@include see-also-note.texi
diff --git a/doc/whats-new-in-2.1.txt b/doc/whats-new-in-2.1.txt
index ef8b23384..1ea68dc2d 100644
--- a/doc/whats-new-in-2.1.txt
+++ b/doc/whats-new-in-2.1.txt
@@ -1,873 +1,873 @@
━━━━━━━━━━━━━━━━━━━━━━━━━━━
GNUPG - WHAT’S NEW IN 2.1
Werner Koch
━━━━━━━━━━━━━━━━━━━━━━━━━━━
2017-08-28
Table of Contents
─────────────────
1 What’s new in GnuPG 2.1
.. 1.1 Removal of the secret keyring
.. 1.2 Removal of PGP-2 support
.. 1.3 Leaner key generation interface
.. 1.4 Support for ECC
.. 1.5 Quick generate and sign commands
.. 1.6 Improved Pinentry support
.. 1.7 Auto-start of the gpg-agent
.. 1.8 Duplicate long key id fixes
.. 1.9 Enhanced Dirmngr
.. 1.10 Better keyserver pool support
.. 1.11 Faster keyring format
.. 1.12 Auto-generated revocation certificates
.. 1.13 Improved card support
.. 1.14 New format for key listings
.. 1.15 Recipient key from file
.. 1.16 Using gpg as a filter
.. 1.17 Support for Putty
.. 1.18 Export of SSH public keys
.. 1.19 Improved X.509 certificate creation
.. 1.20 Scripts to create a Windows installer
A possibly revised version of this article can be found at:
https://gnupg.org/faq/whats-new-in-2.1.html
1 What’s new in GnuPG 2.1
═════════════════════════
GnuPG version 2.1 (now known as 2.2) comes with a bag of new features
which changes some things old-timers are used to. This page explains
the more important ones. It expects that the reader is familiar with
GnuPG version 2.0 and aware that GnuPG consists of /gpg/, /gpgsm/, and
/gpg-agent/ as its main components.
• The file /secring.gpg/ is not anymore used to store the secret keys.
Merging of secret keys is now supported.
• All support for /PGP-2 keys/ has been removed for security reasons.
• The standard key generation interface is now much leaner. This will
help a new user to quickly generate a suitable key.
• Support for /Elliptic Curve Cryptography/ (ECC) is now available.
• Commands to create and sign keys from the command line without any
extra prompts are now available.
• The Pinentry may now show the new passphrase entry and the
passphrase confirmation entry in one dialog.
• There is no more need to manually start the gpg-agent. It is now
started by any part of GnuPG as needed.
• Problems with importing keys with the same long key id have been
addressed.
• The /dirmngr/ is now part of GnuPG proper and also takes care of
accessing keyserver.
• Keyserver pools are now handled in a smarter way.
• A new format for locally storing the public keys is now used. This
considerable speeds up operations on large keyrings.
• /Revocation certificates/ are now created by default.
• Card support has been updated, new readers and token types are
supported.
• The format of the key listing has been changed to better identify
the properties of a key.
• A file with the recipient’s key may now be used directly.
• Gpg can be used to filter out parts of a key.
• The gpg-agent may now be used on Windows as /pageant/ replacement
for /putty/ in the same way it is used for years on Unix as
/ssh-agent/ replacement.
• Creation of X.509 certificates has been improved. It is now also
possible to export them directly in PKCS#8 and PEM format for use on
TLS servers.
• Export of /ssh/ keys has been integrated.
• The scripts to create a Windows installer are now part of GnuPG.
Now for the detailed description of these new features. Note that the
examples assume that /gpg/ is installed as /gpg/. Your installation
may have it installed under the name /gpg2/.
1.1 Removal of the secret keyring
─────────────────────────────────
gpg used to keep the public key pairs in two files: `pubring.gpg' and
`secring.gpg'. The only difference is that secring stored in addition
to the public part also the private part of the key pair. The secret
keyring thus contained only the keys for which a private key is
available, that is the user’s key. It required a lot of code to keep
both versions of the key in sync and led to sometimes surprising
inconsistencies.
The design of GnuPG-2 demands that only the gpg-agent has control over
the private parts of the keys and the actual encryption engine (gpg or
gpgsm) does not know about the private key but care only about session
keys and keys for symmetric encryption. This has been implemented
about 10 years ago for /gpgsm/ (the S/MIME part of GnuPG). However,
/gpg/ (the OpenPGP part) used the gpg-agent only as passphrase entry
and cache device but handles the private key itself.
With GnuPG 2.1 this changed and /gpg/ now also delegates all private
key operations to the gpg-agent. Thus there is no more code in the
/gpg/ binary for handling private keys. En passant this allows the
long time requested “merging of secret keys” and several other
advanced key management techniques.
To ease the migration to the no-secring method, /gpg/ detects the
presence of a `secring.gpg' and converts the keys on-the-fly to the
the key store of /gpg-agent/ (this is the `private-keys-v1.d'
directory below the GnuPG home directory (`~/.gnupg')). This is done
only once and an existing `secring.gpg' is then not anymore touched by
/gpg/. This allows co-existence of older GnuPG versions with GnuPG
2.1. However, any change to the private keys using the new /gpg/ will
not show up when using pre-2.1 versions of GnuPG and vice versa.
Note that the command `--export-secret-keys' still creates an OpenPGP
compliant file with the secret keys. This is achieved by asking
/gpg-agent/ to convert a key and return it in the OpenPGP protected
format. The export operation requires that the passphrase for the key
is entered so that /gpg-agent/ is able to change the protection from
its internal format to the OpenPGP required format.
1.2 Removal of PGP-2 support
────────────────────────────
Some algorithms and parts of the protocols as used by the 20 years old
[PGP-2] software are meanwhile considered unsafe. In particular the
baked in use of the [MD5] hash algorithm limits the security of PGP-2
keys to non-acceptable rate. Technically those PGP-2 keys are called
version 3 keys (v3) and are easily identified by a shorter fingerprint
which is commonly presented as 16 separate double hex digits.
With GnuPG 2.1 all support for those keys has gone. If they are in an
existing keyring they will eventually be removed. If GnuPG encounters
such a key on import it will not be imported due to the not anymore
implemented v3 key format. Removing the v3 key support also reduces
complexity of the code and is thus better than to keep on handling
them with a specific error message.
There is one use case where PGP-2 keys may still be required: For
existing encrypted data. We suggest to keep a version of GnuPG 1.4
around which still has support for these keys (it might be required to
use the `--allow-weak-digest-algos' option). A better solution is to
re-encrypt the data using a modern key.
[PGP-2] https://en.wikipedia.org/wiki/Pretty_Good_Privacy
[MD5] https://en.wikipedia.org/wiki/MD5
1.3 Leaner key generation interface
───────────────────────────────────
This is best shown with an example:
┌────
│ $ gpg --gen-key
│ gpg (GnuPG) 2.1.0; Copyright (C) 2014 Free Software Foundation, Inc.
│ This is free software: you are free to change and redistribute it.
│ There is NO WARRANTY, to the extent permitted by law.
│ gpg: keybox '/home/foo/.gnupg/pubring.kbx' created
│ Note: Use "gpg --full-gen-key" for a full featured key generation dialog.
│ GnuPG needs to construct a user ID to identify your key.
│ Real name: Glenn Greenwald
│ Email address: glenn@example.org
│ You selected this USER-ID:
│ "Glenn Greenwald <glenn@example.org>"
│ Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
│ [...]
│ pub rsa2048/68FD0088 2014-11-03
│ Key fingerprint = 0290 5ABF 17C7 81FB C390 9B00 636A 1BBD 68FD 0088
│ uid [ultimate] Glenn Greenwald <glenn@example.org>
│ sub rsa2048/84439DCD 2014-11-03
└────
Thus only the name and the mail address are required. For all other
parameters the default values are used. Many graphical frontends
works in the same way. Note that /gpg/ prints a hint for the old time
gpg users on how to get the full option menu.
1.4 Support for ECC
───────────────────
GnuPG now support Elliptic Curve keys for public key encryption. This
is defined in [RFC-6637]. Because there is no other mainstream
OpenPGP implementation yet available which supports ECC, the use of
such keys is still very limited. Thus GnuPG 2.1 currently hides the
options to create an ECC key.
For those who want to experiment with ECC or already want to prepare a
key for future use, the command `--full-gen-key' along with the option
`--expert' is the enabler:
┌────
│ $ gpg --expert --full-gen-key
│ gpg (GnuPG) 2.1.0; Copyright (C) 2014 Free Software Foundation, Inc.
│ This is free software: you are free to change and redistribute it.
│ There is NO WARRANTY, to the extent permitted by law.
│ Please select what kind of key you want:
│ (1) RSA and RSA (default)
│ (2) DSA and Elgamal
│ (3) DSA (sign only)
│ (4) RSA (sign only)
│ (7) DSA (set your own capabilities)
│ (8) RSA (set your own capabilities)
│ (9) ECC and ECC
│ (10) ECC (sign only)
│ (11) ECC (set your own capabilities)
│ Your selection? 9
│ Please select which elliptic curve you want:
│ (2) NIST P-256
│ (3) NIST P-384
│ (4) NIST P-521
│ (5) Brainpool P-256
│ (6) Brainpool P-384
│ (7) Brainpool P-512
│ Your selection? 2
│ Please specify how long the key should be valid.
│ 0 = key does not expire
│ <n> = key expires in n days
│ <n>w = key expires in n weeks
│ <n>m = key expires in n months
│ <n>y = key expires in n years
│ Key is valid for? (0)
│ Key does not expire at all
│ Is this correct? (y/N) y
│ GnuPG needs to construct a user ID to identify your key.
│ Real name: Edward Snowden
│ Email address: edward@example.org
│ Comment:
│ You selected this USER-ID:
│ "Edward Snowden <edward@example.org>"
│ Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
│ [...]
│ pub nistp256/382660E3 2014-11-03
│ Key fingerprint = E630 27CF 3D68 22A7 6FF2 093E D179 9E72 3826 60E3
│ uid [ultimate] Edward Snowden <edward@example.org>
│ sub nistp256/48C9A997 2014-11-03 nistp256
└────
In this example we created a primary ECC key for signing and an subkey
for encryption. For both we use the NIST P-256 curve. The key may
now be used in the same way as any other key. It is possible to add
an RSA subkey or one can create an RSA or DSA main key and add an ECC
subkey for signing or encryption. Note that the list of offered
curves depends on the installed Libgcrypt version.
For many people the NIST and also the Brainpool curves have an
doubtful origin and thus the plan for GnuPG is to use Bernstein’s
[Curve 25519] as default. GnuPG 2.1.0 already comes with support for
signing keys using the [Ed25519] variant of this curve. This has not
yet been standardized by the IETF (i.e. there is no RFC) but we won’t
wait any longer and go ahead using the proposed format for this
signing algorithm. The format for an encryption key has not yet been
finalized and will be added to GnuPG in one of the next point
releases. Recall that an encryption subkey can be added to a key at
any time. If you want to create a signing key you may do it this way:
┌────
│ $ gpg --expert --full-gen-key
│ gpg (GnuPG) 2.1.0; Copyright (C) 2014 Free Software Foundation, Inc.
│ This is free software: you are free to change and redistribute it.
│ There is NO WARRANTY, to the extent permitted by law.
│ Please select what kind of key you want:
│ (1) RSA and RSA (default)
│ (2) DSA and Elgamal
│ (3) DSA (sign only)
│ (4) RSA (sign only)
│ (7) DSA (set your own capabilities)
│ (8) RSA (set your own capabilities)
│ (9) ECC and ECC
│ (10) ECC (sign only)
│ (11) ECC (set your own capabilities)
│ Your selection? 10
│ Please select which elliptic curve you want:
│ (1) Curve 25519
│ (2) NIST P-256
│ (3) NIST P-384
│ (4) NIST P-521
│ (5) Brainpool P-256
│ (6) Brainpool P-384
│ (7) Brainpool P-512
│ Your selection? 1
│ gpg: WARNING: Curve25519 is not yet part of the OpenPGP standard.
│ Use this curve anyway? (y/N) y
│ Please specify how long the key should be valid.
│ 0 = key does not expire
│ <n> = key expires in n days
│ <n>w = key expires in n weeks
│ <n>m = key expires in n months
│ <n>y = key expires in n years
│ Key is valid for? (0)
│ Key does not expire at all
│ Is this correct? (y/N) y
│ GnuPG needs to construct a user ID to identify your key.
│ Real name: Laura Poitras
│ Email address: laura@example.org
│ Comment:
│ You selected this USER-ID:
│ "Laura Poitras <laura@example.org>"
│ Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
│ [...]
│ pub ed25519/5C1AFC2A 2014-11-03
│ Key fingerprint = ED85 4D98 5D8F 502F C6C5 FFB2 AA81 319E 5C1A FC2A
│ uid [ultimate] Laura Poitras <laura@example.org>
└────
Support for ECC keys is available only on some keyservers but it is
expected that this will be fixed over the next few months.
[RFC-6637] https://rfc-editor.org/info/rfc6637
[Curve 25519] http://cr.yp.to/ecdh/curve25519-20060209.pdf
[Ed25519] http://dx.doi.org/10.1007/s13389-012-0027-1
1.5 Quick generate and sign commands
────────────────────────────────────
Sometimes it is useful to use only command line options without any
parameter file or interactive prompts for generating a key or to sign
a key. This can now be accomplished with a few new commands:
┌────
│ $ gpg --batch --quick-gen-key 'Daniel Ellsberg <ellsberg@example.org>'
│ gpg: key 911B90A9 marked as ultimately trusted
└────
If a key with that user id already exists, gpg bails out with an error
message. You can force creation using the option `--yes'. If you
want some more control, you may not use `--batch' and gpg will ask for
confirmation and show the resulting key:
┌────
│ $ gpg --quick-gen-key 'Daniel Ellsberg <ellsberg@example.org>'
│ About to create a key for:
│ "Daniel Ellsberg <ellsberg@example.org>"
│ Continue? (Y/n) y
│ gpg: A key for "Daniel Ellsberg <ellsberg@example.org>" already exists
│ Create anyway? (y/N) y
│ gpg: creating anyway
│ [...]
│ pub rsa2048/BD19AC1C 2014-11-04
│ Key fingerprint = 15CB 723E 2000 A1A8 2505 F3B7 CC00 B501 BD19 AC1C
│ uid [ultimate] Daniel Ellsberg <ellsberg@example.org>
│ sub rsa2048/72A4D018 2014-11-04
└────
Another common operation is to sign a key. /gpg/ can do this directly
from the command line by giving the fingerprint of the to-be-signed
key:
┌────
│ $ gpg --quick-sign-key '15CB 723E 2000 A1A8 2505 F3B7 CC00 B501 BD19 AC1C'
│ pub rsa2048/BD19AC1C
│ created: 2014-11-04 expires: never usage: SC
│ trust: ultimate validity: ultimate
│ Primary key fingerprint: 15CB 723E 2000 A1A8 2505 F3B7 CC00 B501 BD19 AC1C
│ Daniel Ellsberg <ellsberg@example.org>
└────
In case the key has already been signed, the command prints a note and
exits with success. In case you want to check that it really worked,
use `--check-sigs' as usual:
┌────
│ $ gpg --check-sigs '15CB 723E 2000 A1A8 2505 F3B7 CC00 B501 BD19 AC1C'
│ gpg: checking the trustdb
│ gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
│ gpg: depth: 0 valid: 6 signed: 1 trust: 0-, 0q, 0n, 0m, 0f, 6u
│ pub rsa2048/BD19AC1C 2014-11-04
│ uid [ full ] Daniel Ellsberg <ellsberg@example.org>
│ sig!3 BD19AC1C 2014-11-04 Daniel Ellsberg <ellsberg@example.org>
│ sig! 68FD0088 2014-11-04 Glenn Greenwald <glenn@example.org>
│ sub rsa2048/72A4D018 2014-11-04
│ sig! BD19AC1C 2014-11-04 Daniel Ellsberg <ellsberg@example.org>
└────
The fingerprint may also be given without the spaces in which case
there is no need for the quotes. If you want to sign only certain
user ids of a key, list those user id verbatim after the fingerprint.
To create a non-exportable key signature, use the command
`--quick-lsign-key' instead.
Since version 2.1.4 it possible to directly add another user id to an
existing key:
┌────
│ $ gpg -k 8CFDE12197965A9A
│ pub ed25519/8CFDE12197965A9A 2014-08-19
│ uid [ unknown] EdDSA sample key 1
│ $ gpg --quick-adduid 8CFDE12197965A9A 'Sample 2 <me@example.org>'
│ $ gpg -k 8CFDE12197965A9A
│ pub ed25519/8CFDE12197965A9A 2014-08-19
│ uid [ unknown] Sample 2 <me@example.org>
│ uid [ unknown] EdDSA sample key 1
└────
Since version 2.1.13 another subkey can directly be added to an
existing key:
┌────
│ $ gpg --quick-addkey 15CB723E2000A1A82505F3B7CC00B501BD19AC1C - - 2016-12-31
│ $ gpg -k 15CB723E2000A1A82505F3B7CC00B501BD19AC1C
│ pub rsa2048 2014-11-04 [SC]
│ 15CB723E2000A1A82505F3B7CC00B501BD19AC1C
│ uid [ unknown] Daniel Ellsberg <ellsberg@example.org>
│ sub rsa2048 2014-11-04 [E]
│ sub rsa2048 2016-06-06 [E] [expires: 2016-12-31]
└────
Here we created another encryption subkey with an expiration date.
The key listing also shows the default key listing format introduced
with 2.1.13. There are a lot of other options to the `--quick-addkey'
command which are described in the manual.
Since version 2.1.14 it possible to revoke a user id on an existing
key:
┌────
│ $ gpg -k 8CFDE12197965A9A
│ pub ed25519/8CFDE12197965A9A 2014-08-19
│ uid [ unknown] Sample 2 <me@example.org>
│ uid [ unknown] EdDSA sample key 1
│ $ gpg --quick-revuid 8CFDE12197965A9A 'EdDSA sample key 1'
│ $ gpg -k 8CFDE12197965A9A
│ pub ed25519/8CFDE12197965A9A 2014-08-19
│ uid [ unknown] Sample 2 <me@example.org>
└────
Since version 2.1.17 the expiration date of the primary key can be
changed directly:
┌────
│ $ gpg --quick-set-expire 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7 2017-12-31
│ $ gpg -K 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7
│ sec rsa2048 2016-06-22 [SC] [expires: 2017-12-31]
│ 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7
│ uid [ultimate] steve.biko@example.net
│ ssb rsa2048 2016-06-22 [E]
│ $ gpg --quick-set-expire 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7 none
│ $ gpg -K 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7
│ sec rsa2048 2016-06-22 [SC]
│ 5B83120DB1E3A65AE5A8DCF6AA43F1DCC7FED1B7
│ uid [ultimate] steve.biko@example.net
│ ssb rsa2048 2016-06-22 [E]
└────
1.6 Improved Pinentry support
─────────────────────────────
When using a recent Pinentry module (0.90, GTK+ variant), the
gpg-agent will not anymore show two separate Pinentry dialogs to enter
a new passphrase and later to confirm the new passphrase. Instead the
first dialog also has the confirm/repeat entry and internally checks
whether they match.
With any Pinentry version the several separate dialogs to inform and
ask for confirmation about questionable properties of a new passphrase
(e.g. length, only alpha letters) have been combined into one dialog
to show all non-asserted constraints at once.
The GTK+ Pinentry does now allow pasting of values into the entries.
Copying them from the entries is still inhibited on purpose.
Depending on the system, the option `no-grab' may be required for in
the `gpg-agent.conf' file to actually make use of the paste feature.
1.7 Auto-start of the gpg-agent
───────────────────────────────
The /gpg-agent/ is the central part of the GnuPG system. It takes
care of all private (secret) keys and if required diverts operations
to a smartcard or other token. It also provides support for the
Secure Shell by implementing the ssh-agent protocol.
The classic way to run /gpg-agent/ on Unix systems is by launching it
at login time and use an environment variable (`GPG_AGENT_INFO') to
tell the other GnuPG modules how to connect to the agent. However,
correctly managing the start up and this environment variable is
cumbersome so that an easier method is required. Since GnuPG 2.0.16
the `--use-standard-socket' option already allowed to start the agent
on the fly; however the environment variable was still required.
With GnuPG 2.1 the need of `GPG_AGENT_INFO' has been completely
removed and the variable is ignored. Instead a fixed Unix domain
socket named `S.gpg-agent' in the GnuPG home directory (by default
`~/.gnupg') is used. The agent is also started on demand by all tools
requiring services from the agent.
If the option `--enable-ssh-support' is used the auto-start mechanism
does not work because /ssh/ does not know about this mechanism.
Instead it is required that the environment variable `SSH_AUTH_SOCK'
is set to the `S.gpg-agent.ssh' socket in the GnuPG home directory.
Further /gpg-agent/ must be started: Either by using a GnuPG command
which implicitly starts /gpg-agent/ or by using `gpgconf --launch
gpg-agent' to explicitly start it if not yet done.
1.8 Duplicate long key id fixes
───────────────────────────────
A deficit of the OpenPGP protocol is that signatures carry only a
limited indication on which public key has been used to create a
signature. Thus a verification engine may only use this “long key id”
to look up the key in its own store or from a public keyserver.
Unfortunately it has now become possible to create a key with a long
key id matching the key id of another key. Importing a key with a
long key id already used by another key in gpg’s local key store was
not possible due to checks done on import. Now, if the “wrong” key
has been imported first /gpg/ would not allow later import of the
second “correct” key. This problem has been fixed in 2.1 by allowing
the import and by doing trial verification against all matching keys.
1.9 Enhanced Dirmngr
────────────────────
Before version 2.1, /gpg/ used so-called keyserver helpers to access
the OpenPGP keyservers. A problem with that is that they are short
living processes which are not able to keep a state. With 2.1, the
formerly separate package Dirmngr (which was separate due to copyright
assignment reasons) has been integrated into GnuPG.
In the past /dirmngr/ was only used by /gpgsm/ for X.509 (S/MIME) CRL
and OCSP handling. Being a proper part of GnuPG /dirmngr/ does now
also care about accessing OpenPGP keyservers. This make its easier to
debug problems with the keyservers and to exchange additional
information about the keyserver between /gpg/ and /dirmngr/. It will
eventually also be possible to run background tasks to refresh keys.
Although the ability to start /dirmngr/ as a system service is still
available, this is not anymore recommended and instead /dirmngr/ is
now by default started on-demand, very similar to /gpg-agent/.
1.10 Better keyserver pool support
──────────────────────────────────
For load balancing reasons, keyservers are organized in pools to
enable instant round-robin DNS assignment of random keyservers. A
problem with that approach is that the DNS resolver is not aware of
the state of the keyserver. If a keyserver has gone down or a routing
problems occurs, /gpg/ and its keyserver helpers were not aware of it
and would try over and over to use the same, dead, keyserver up until
the DNS information expires and a the DNS resolver assigned a new
server from the pool.
The new /dirmngr/ in GnuPG does not use the implicit round-robin of
the DNS resolver but uses its own DNS lookup and keeps an internal
table of all hosts from the pool along with the encountered aliveness
state. Thus after a failure (timeout) of a request, /dirmngr/ flags a
host as dead and randomly selects another one from the pool. After a
few hours the flag is removed so that the host will be tried again.
It is also possible to mark a specific host from a pool explicitly as
dead so that it won’t be used in the future. To interact with the
/dirmngr/ the `gpg-connect-agent' tool is used:
┌────
│ $ gpg-connect-agent --dirmngr 'help keyserver' /bye
│ $ gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye
└────
The first command prints a help screen for the keyserver command and
the second command prints the current host table.
1.11 Faster keyring format
──────────────────────────
The format GnuPG has always used for the public keyring is actually a
slightly extended version of the on-the-wire format for OpenPGP key
exchange. This format is quite inflexible to work with when random
access to keys in the keyring is required. In fact /gpg/ always
parsed all keys in the keyring until it encountered the desired one.
With a large keyring (more than a few thousand keys) this could be
quite slow.
From its very beginning /gpgsm/ has used a different format to store
public keys (certificates) which we call a /keybox/. That file format
carries meta information about the stored keys and thus allows
searching without actually parsing the key and computing fingerprints
and such. The /keybox/ format has been designed to be protocol
independent and with 2.1 support for OpenPGP keys has been added.
Random access to the keys is now really fast and keyrings with 30000
keys and more are now easily possible. That change also enables us to
easily introduce other storage methods
If no `pubring.gpg' is found, /gpg/ defaults to the new /keybox/
format and creates a `pubring.kbx' keybox file. If such a keybox file
already exists, for example due to the use of /gpgsm/, it will also be
used for OpenPGP keys. However, if a `pubring.gpg' is found and no
keybox file with OpenPGP keys exists, the old `pubring.gpg' will be
used. Take care: GnuPG versions before 2.1 will always use the
`pubring.gpg' file and not know anything about keys stored in the
keybox file.
To convert an existing `pubring.gpg' file to the keybox format, you
first backup the ownertrust values, then rename the file to (for
example) `publickeys', so it won’t be recognized by any GnuPG version,
then run import, and finally restore the ownertrust values:
┌────
│ $ cd ~/.gnupg
│ $ gpg --export-ownertrust >otrust.lst
│ $ mv pubring.gpg publickeys
│ $ gpg --import-options import-local-sigs --import publickeys
│ $ gpg --import-ownertrust otrust.lst
└────
You may then rename the `publickeys' file back so that it can be used
by older GnuPG versions. Remember that in this case you have two
independent copies of the public keys. The ownertrust values are kept
by all gpg versions in the file `trustdb.gpg' but the above
precautions need to be taken to keep them over an import.
1.12 Auto-generated revocation certificates
───────────────────────────────────────────
This version creates an ASCII armored revocation certificate for each
generated keypair and stores that certificate in a file named after
the fingerprint of the key in the `openpgp-revocs.d' directory below
the GnuPG home directory. Brief instructions on how to use this
revocation certificate are put at the top of the file.
1.13 Improved card support
──────────────────────────
The /scdaemon/, which is responsible for accessing smardcards and
other tokens, has received many updates. In particular pluggable USB
readers with a fixed card now work smoothless and similar to standard
readers. The latest features of the [gnuk] token are supported. Code
for the SmartCard-HSM has been added. More card readers with a PIN
pad are supported. The internal CCID driver does now also work with
certain non-auto-configuration equipped readers.
Since version 2.1.19 multiple card readers are support and the format
of the Pinentry prompts has been changed to show more information on
the requested card.
[gnuk] http://www.fsij.org/doc-gnuk/
1.14 New format for key listings
────────────────────────────────
Due to the introduction of ECC keys the old format to list keys was
not anymore suitable. In particular, the length of an ECC key is
defined but its expressiveness is limited without the other parameters
of the curve. The common way to describe an ECC key is by using the
assigned name of its curve. To allow for a common description we now
either use the algorithm name with appended key length or use the name
of the curve:
┌────
│ pub 2048D/1E42B367 2007-12-31 [expires: 2018-12-31]
│ pub dsa2048 2007-12-31 [SC] [expires: 2018-12-31]
│ 80615870F5BAD690333686D0F2AD85AC1E42B367
│ pub ed25519 2014-10-18 [SC]
│ 0B7F0C1D690BC440D5AFF9B56902F00A0AA914C9
└────
The first two "pub"-items show the same key in the old format and in
the new format. The third "pub"-item shows an example of an ECC key
using an ed25519 curve. Note that since version 2.1.13 the key id is
not anymore shown. Instead the full fingerprint is shown in a compact
format; by using the option `--with-fingerprint' the non-compact
format is used. The `--keyid-format' option can be used to switch
back to the discouraged format which prints only the key id.
As a further change the validity of a key is now shown by default;
that is `show-uid-validity' is implicitly used for the
`--list-options'.
The annotated key listing produced by the `--with-colons' options did
not change. However a couple of new fields have been added, for
example if the new option `--with-secret' is used the “S/N of a token
field” indicates the presence of a secret key even in a public key
listing. This option is supported by recent [GPGME] versions and
makes writing of key manager software easier.
[GPGME] https://gnupg.org/software/gpgme/
1.15 Recipient key from file
────────────────────────────
Since version 2.1.14 it is possible to specify the recipient’s key by
providing a file with that key. This done with the new options
`--recipient-file' (or short `-f') and `--hidden-recipient-file' (or
short `-F'). The file must containing exactly one key in binary or
armored format. All keys specified with those options are always
considered fully valid. These option may be mixed with the regular
options to specify a key. Along with the new convenience option
`--no-keyring' it is now possible to encrypt data without maintaining
a local keyring.
1.16 Using gpg as a filter
──────────────────────────
Since version 2.1.14 the export and import options have been enhanced
- to allow the use of /gpg/ to modify a key without first stroing it in
+ to allow the use of /gpg/ to modify a key without first storing it in
the keyring. For example:
┌────
│ $ gpg --import-options import-minimal,import-export \
│ --output smallkey.gpg --import key.gpg
└────
copies the keys in `keys.gpg' to `smallkey.gpg' while also removing
all key signatures except for the latest self-signatures. This can
even be further restricted to copy only a specific user ID to the
output file:
┌────
│ $ gpg --import-options import-minimal,import-export \
│ --import-filter keepuid='mbox = foo@example.org' \
│ --output smallkey.gpg --import key.gpg
└────
Here the new `--import-filter' option is used to remove all user IDs
except for those which have the mail address “foo@example.org”. The
same is also possible while exporting a key:
┌────
│ $ gpg --export-filter keepuid='mbox = me@example.org' \
│ --armor --export 8CFDE12197965A9A >smallkey.asc
└────
1.17 Support for Putty
──────────────────────
On Windows the new option `--enable-putty-support' allows gpg-agent to
act as a replacement for [Putty]’s authentication agent /Pageant/. It
is the Windows counterpart for the `--enable-ssh-support' option as
used on Unix.
[Putty] http://www.chiark.greenend.org.uk/~sgtatham/putty/
1.18 Export of SSH public keys
──────────────────────────────
The new command `--export-ssh-key' makes it easy to export an /ssh/
public key in the format used for ssh’s `authorized_keys' file. By
default the command exports the newest subkey with an authorization
usage flags. A special syntax can be used to export other subkeys.
This command is available since 2.1.11 and replaces the former debug
utility /gpgkey2ssh/.
1.19 Improved X.509 certificate creation
────────────────────────────────────────
In addition to an improved certificate signing request menu, it is now
possible to create a self-signed certificate using the interactive
menu of /gpgsm/.
In batch mode the certificate creation dialog can now be controlled by
a parameter file with several new keywords. Such a parameter file
allows the creation of arbitrary X.509 certificates similar to what
can be done with /openssl/. It may thus be used as the base for a CA
software. For details see the “CSR and certificate creation” section
in the manual.
The new commands `--export-secret-key-p8' and –export-secret-key-raw=
may be used to export a secret key directly in PKCS#8 or PKCS#1
format. Thus X.509 certificates for TLS use may be managed by /gpgsm/
and directly exported in a format suitable for OpenSSL based servers.
1.20 Scripts to create a Windows installer
──────────────────────────────────────────
GnuPG now comes with the /speedo/ build system which may be used to
quickly download and build GnuPG and all its direct dependencies on a
decent Unix system. See the README file for more instructions.
The very same script may also be used to build a complete NSIS based
installer for Windows using the mingw-w64 cross-compiler toolchain.
That installer will feature GnuPG proper, GPA as graphical frontend,
and GpgEX as a Windows Explorer extension. GnuPG needs to be unpacked
and from the top source directory you run this command
┌────
│ make -f build-aux/speedo.mk w32-installer
└────
This command downloads all direct dependencies, checks the signatures
using the GnuPG version from the build system (all Linux distros
feature a suitable GnuPG tool), builds everything from source, and
uses NSIS to create the installer. Although this sounds easy, some
experience in setting up a development machine is still required.
Some versions of the toolchain exhibit bugs and thus your mileage may
vary. See the [Wiki] for more info.
Support for keyserver access over TLS is currently not available but
will be added with one of the next point releases.
# Copyright 2014--2017 The GnuPG Project.
# This work is licensed under the Creative Commons
# Attribution-ShareAlike 4.0 International License. To view a copy of
# this license, visit http://creativecommons.org/licenses/by-sa/4.0/
# or send a letter to Creative Commons, PO Box 1866, Mountain View, CA
# 94042, USA.
#
# The canonical source for this article can be found in the gnupg-doc
# git repository as web/faq/whats-new-in-2.1.org.
[Wiki] https://wiki.gnupg.org/Build2.1_Windows
diff --git a/g10/call-agent.c b/g10/call-agent.c
index 7e56d8d14..e638b83eb 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -1,3027 +1,3027 @@
/* call-agent.c - Divert GPG operations to the agent.
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/status.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/ttyio.h"
#define CONTROL_D ('D' - 'A' + 1)
static assuan_context_t agent_ctx = NULL;
static int did_early_card_test;
struct confirm_parm_s
{
char *desc;
char *ok;
char *notok;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
struct confirm_parm_s *confirm;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* If RC is not 0, write an appropriate status message. */
static void
status_sc_op_failure (int rc)
{
switch (gpg_err_code (rc))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
}
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
const char *s;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpg_proxy_pinentry_notify (parm->ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
if (have_static_passphrase ())
{
s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
{
char *pw;
char buf[32];
if (parm->keyinfo.keyid)
emit_status_need_passphrase (parm->ctrl,
parm->keyinfo.keyid,
parm->keyinfo.mainkeyid,
parm->keyinfo.pubkey_algo);
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: "));
cpr_kill_prompt ();
if (*pw == CONTROL_D && !pw[1])
err = gpg_error (GPG_ERR_CANCELED);
else
err = assuan_send_data (parm->ctx, pw, strlen (pw));
xfree (pw);
}
}
else if ((s = has_leading_keyword (line, "CONFIRM"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& parm->confirm)
{
int ask = atoi (s);
int yes;
if (ask)
{
yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc);
if (yes)
err = assuan_send_data (parm->ctx, NULL, 0);
else
err = gpg_error (GPG_ERR_NOT_CONFIRMED);
}
else
{
tty_printf ("%s", parm->confirm->desc);
err = assuan_send_data (parm->ctx, NULL, 0);
}
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
" ", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl, int flag_for_card)
{
int rc;
(void)ctrl; /* Not yet used. */
/* Fixme: We need a context for each thread or serialize the access
to the agent. */
if (agent_ctx)
rc = 0;
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc
&& !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
write_status_error ("set_pinentry_mode", rc);
}
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
write_status_error ("set_request_origin", rc);
}
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!rc && flag_for_card && !did_early_card_test)
{
/* Request the serial number of the card for an early test. */
struct agent_card_info_s info;
memset (&info, 0, sizeof info);
if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!rc)
rc = assuan_transact (agent_ctx,
opt.flags.use_only_openpgp_card?
"SCD SERIALNO openpgp" : "SCD SERIALNO",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
{
switch (gpg_err_code (rc))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
write_status_text (STATUS_CARDCTRL, "6");
break;
case GPG_ERR_OBJ_TERM_STATE:
write_status_text (STATUS_CARDCTRL, "7");
break;
default:
write_status_text (STATUS_CARDCTRL, "4");
log_info ("selecting card failed: %s\n", gpg_strerror (rc));
break;
}
}
if (!rc && is_status_enabled () && info.serialno)
{
char *buf;
buf = xasprintf ("3 %s", info.serialno);
write_status_text (STATUS_CARDCTRL, buf);
xfree (buf);
}
agent_release_card_info (&info);
if (!rc)
did_early_card_test = 1;
}
return rc;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Release the card info structure INFO. */
void
agent_release_card_info (struct agent_card_info_s *info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->apptype); info->apptype = NULL;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
info->fpr1len = info->fpr2len = info->fpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct agent_card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& xtoi_2 (parm->serialno+12) >= 2 );
}
else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptype);
parm->apptype = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
{
while (spacep (p))
p++;
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvretry[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
xfree (buf);
}
}
else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1);
else if (no == 2)
parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2);
else if (no == 3)
parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
{
int no = atoi (line);
while (* line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1time = strtoul (line, NULL, 10);
else if (no == 2)
parm->fpr2time = strtoul (line, NULL, 10);
else if (no == 3)
parm->fpr3time = strtoul (line, NULL, 10);
}
else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
const char *hexgrp = line;
int no;
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (strncmp (line, "OPENPGP.", 8))
;
else if ((no = atoi (line+8)) == 1)
unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1);
else if (no == 2)
unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2);
else if (no == 3)
unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3);
}
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof parm->cafpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = PUBKEY_ALGO_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
return 0;
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
NULL, NULL);
}
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3))
{
unsigned char *data = unescape_status_string (line);
if (data[2] != 0x03)
parm->kdf_do_enabled = 0;
else if (data[22] != 0x85)
parm->kdf_do_enabled = 1;
else
parm->kdf_do_enabled = 2;
xfree (data);
}
else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
return 0;
}
/* Call the scdaemon to learn about a smartcard. Note that in
* contradiction to the function's name, gpg-agent's LEARN command is
* used and not the low-level "SCD LEARN".
* Used by:
* card-util.c
* keyedit_menu
* card_store_key_with_backup (Woth force to remove secret key data)
*/
int
agent_scd_learn (struct agent_card_info_s *info, int force)
{
int rc;
struct default_inq_parm_s parm;
struct agent_card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx,
force ? "LEARN --sendinfo --force" : "LEARN --sendinfo",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get the key attributes. */
if (!rc)
agent_scd_getattr ("KEY-ATTR", info);
if (info == &dummyinfo)
agent_release_card_info (info);
return rc;
}
struct keypairinfo_cb_parm_s
{
keypair_info_t kpinfo;
keypair_info_t *kpinfo_tail;
};
/* Callback for the agent_scd_keypairinfo function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
struct keypairinfo_cb_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL;
keypair_info_t kpi = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*/
char *fields[4];
int nfields;
const char *hexgrp, *keyref, *usage;
time_t atime;
u32 keytime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - invalid status line - ignore */
hexgrp = fields[0];
keyref = fields[1];
if (nfields > 2)
usage = fields[2];
else
usage = "";
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
keytime = atime;
}
else
keytime = 0;
kpi = xtrycalloc (1, sizeof *kpi);
if (!kpi)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (*hexgrp == 'X' && !hexgrp[1])
*kpi->keygrip = 0; /* No hexgrip. */
else if (strlen (hexgrp) == 2*KEYGRIP_LEN)
mem2str (kpi->keygrip, hexgrp, sizeof kpi->keygrip);
else
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (!*keyref)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
kpi->idstr = xtrystrdup (keyref);
if (!kpi->idstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kpi->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kpi->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kpi->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kpi->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
kpi->keytime = keytime;
/* Append to the list. */
*parm->kpinfo_tail = kpi;
parm->kpinfo_tail = &kpi->next;
kpi = NULL;
}
leave:
free_keypair_info (kpi);
xfree (line_buffer);
return err;
}
/* Read the keypairinfo lines of the current card directly from
* scdaemon. The list is returned as a string made up of the keygrip,
* a space and the keyref. The flags of the string carry the usage
* bits. If KEYREF is not NULL, only a single string is returned
* which matches the given keyref. */
gpg_error_t
agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, keypair_info_t *r_list)
{
gpg_error_t err;
struct keypairinfo_cb_parm_s parm;
struct default_inq_parm_s inq_parm;
char line[ASSUAN_LINELENGTH];
*r_list = NULL;
err= start_agent (ctrl, 1);
if (err)
return err;
memset (&inq_parm, 0, sizeof inq_parm);
inq_parm.ctx = agent_ctx;
parm.kpinfo = NULL;
parm.kpinfo_tail = &parm.kpinfo;
if (keyref)
snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref);
else
snprintf (line, DIM(line), "SCD LEARN --keypairinfo");
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &parm);
if (!err && !parm.kpinfo)
err = gpg_error (GPG_ERR_NO_DATA);
if (err)
free_keypair_info (parm.kpinfo);
else
*r_list = parm.kpinfo;
return err;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW. With HEXAPDU being NULL only a RESET command is
* send to scd. With HEXAPDU being the string "undefined" the command
* "SERIALNO undefined" is send to scd.
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
{
gpg_error_t err;
/* Start the agent but not with the card flag so that we do not
autoselect the openpgp application. */
err = start_agent (NULL, 0);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
*r_sw = buf16_to_uint (data+datalen-2);
}
xfree (data);
}
}
return err;
}
/* Used by:
* card_store_subkey
* card_store_key_with_backup
*/
int
agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s",
force?"--force ": "", hexgrip, serialno, keyno, timestamp);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
if (rc)
return rc;
return rc;
}
/* Object used with the agent_scd_getattr_one. */
struct getattr_one_parm_s {
const char *keyword; /* Keyword to look for. */
char *data; /* Malloced and unescaped data. */
gpg_error_t err; /* Error code or 0 on success. */
};
/* Callback for agent_scd_getattr_one. */
static gpg_error_t
getattr_one_status_cb (void *opaque, const char *line)
{
struct getattr_one_parm_s *parm = opaque;
const char *s;
if (parm->data)
return 0; /* We want only the first occurrence. */
if ((s=has_leading_keyword (line, parm->keyword)))
{
parm->data = percent_plus_unescape (s, 0xff);
if (!parm->data)
parm->err = gpg_error_from_syserror ();
}
return 0;
}
/* Simplified version of agent_scd_getattr. This function returns
- * only the first occurance of the attribute NAME and stores it at
+ * only the first occurrence of the attribute NAME and stores it at
* R_VALUE. A nul in the result is silennly replaced by 0xff. On
* error NULL is stored at R_VALUE. */
gpg_error_t
agent_scd_getattr_one (const char *name, char **r_value)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inqparm;
struct getattr_one_parm_s parm;
*r_value = NULL;
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
memset (&inqparm, 0, sizeof inqparm);
inqparm.ctx = agent_ctx;
memset (&parm, 0, sizeof parm);
parm.keyword = name;
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (NULL, 1);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inqparm,
getattr_one_status_cb, &parm);
if (!err && parm.err)
err = parm.err;
else if (!err && !parm.data)
err = gpg_error (GPG_ERR_NO_DATA);
if (!err)
*r_value = parm.data;
else
xfree (parm.data);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command.
*
* Used by:
* build_sk_list
* enum_secret_keys
* get_signature_count
* card-util.c
* generate_keypair (KEY-ATTR)
* card_store_key_with_backup (SERIALNO)
* generate_card_subkeypair (KEY-ATTR)
*/
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return rc;
}
/* Send an setattr command to the SCdaemon.
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen)
{
gpg_error_t err;
const unsigned char *value = value_arg;
char line[ASSUAN_LINELENGTH];
char *p;
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
*p++ = ' ';
for (; valuelen; value++, valuelen--)
{
if (p >= line + DIM(line)-5 )
return gpg_error (GPG_ERR_TOO_LARGE);
if (*value < ' ' || *value == '+' || *value == '%')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
*p = 0;
err = start_agent (NULL, 1);
if (!err)
{
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
}
status_sc_op_failure (err);
return err;
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END
command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
int rc;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
rc = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Send a WRITECERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return rc;
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
write_status_text (STATUS_PROGRESS, line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDEAMON. On success, creation time is stored back to
* CREATETIME.
* Used by:
* gen_card_key
*/
int
agent_scd_genkey (int keyno, int force, u32 *createtime)
{
int rc;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
if (*createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
keyno);
dfltparm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
status_sc_op_failure (rc);
return rc;
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. With DEMAND the active
* card is switched to the card with that serialno.
* Used by:
* card-util.c
* build_sk_list
* enum_secret_keys
*/
int
agent_scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
*r_serialno = serialno;
return 0;
}
/* Send a READCERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, DIM(line), "SCD READCERT %s", certidstr);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error (GPG_ERR_ENOMEM);
return 0;
}
-/* Callback for the agent_scd_readkey fucntion. */
+/* Callback for the agent_scd_readkey function. */
static gpg_error_t
readkey_status_cb (void *opaque, const char *line)
{
u32 *keytimep = opaque;
gpg_error_t err = 0;
const char *args;
char *line_buffer = NULL;
/* FIXME: Get that info from the KEYPAIRINFO line. */
if ((args = has_leading_keyword (line, "KEYPAIRINFO"))
&& !*keytimep)
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*
* Note that we use only the first valid KEYPAIRINFO line. More
* lines are possible if a second card carries the same key.
*/
char *fields[4];
int nfields;
time_t atime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 4)
goto leave; /* not enough args - ignore */
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
*keytimep = atime;
}
else
*keytimep = 0;
}
leave:
xfree (line_buffer);
return err;
}
/* This is a variant of agent_readkey which sends a READKEY command
* directly Scdaemon. On success a new s-expression is stored at
* R_RESULT. If R_KEYTIME is not NULL the key cresation time of an
* OpenPGP card is stored there - if that is not known 0 is stored.
* In the latter case it is allowed to pass NULL for R_RESULT. */
gpg_error_t
agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr,
gcry_sexp_t *r_result, u32 *r_keytime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
struct default_inq_parm_s dfltparm;
u32 keytime;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctx = agent_ctx;
if (r_result)
*r_result = NULL;
if (r_keytime)
*r_keytime = 0;
err = start_agent (ctrl, 1);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, DIM(line),
"SCD READKEY --info%s -- %s",
r_result? "":"-only", keyrefstr);
keytime = 0;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
readkey_status_cb, &keytime);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
if (r_result)
err = gcry_sexp_new (r_result, buf, buflen, 0);
else
err = 0;
xfree (buf);
if (!err && r_keytime)
*r_keytime = keytime;
return err;
}
struct card_cardlist_parm_s {
int error;
strlist_t list;
};
/* Callback function for agent_card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
/* Return a list of currently available cards.
* Used by:
* card-util.c
* skclist.c
*/
int
agent_scd_cardlist (strlist_t *result)
{
int err;
char line[ASSUAN_LINELENGTH];
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
strcpy (line, "SCD GETINFO card_list");
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return 0;
}
struct card_keyinfo_parm_s {
int error;
keypair_info_t list;
};
/* Callback function for agent_card_keylist. */
static gpg_error_t
card_keyinfo_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct card_keyinfo_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
{
const char *s;
int n;
keypair_info_t keyinfo;
keypair_info_t *l_p = &parm->list;
while ((*l_p))
l_p = &(*l_p)->next;
keyinfo = xtrycalloc (1, sizeof *keyinfo);
if (!keyinfo)
{
alloc_error:
if (!parm->error)
parm->error = gpg_error_from_syserror ();
return 0;
}
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (n != 40)
{
parm_error:
if (!parm->error)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
return 0;
}
memcpy (keyinfo->keygrip, line, 40);
keyinfo->keygrip[40] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (*line++ != 'T')
goto parm_error;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n)
goto parm_error;
keyinfo->serialno = xtrymalloc (n+1);
if (!keyinfo->serialno)
goto alloc_error;
memcpy (keyinfo->serialno, line, n);
keyinfo->serialno[n] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (!*line)
goto parm_error;
keyinfo->idstr = xtrystrdup (line);
if (!keyinfo->idstr)
goto alloc_error;
*l_p = keyinfo;
}
return err;
}
/* Free a keypair info list. */
void
free_keypair_info (keypair_info_t l)
{
keypair_info_t l_next;
for (; l; l = l_next)
{
l_next = l->next;
xfree (l->serialno);
xfree (l->idstr);
xfree (l);
}
}
/* Call the scdaemon to check if a key of KEYGRIP is available, or
retrieve list of available keys on cards. With CAP, we can limit
keys with specified capability. On success, the allocated
structure is stored at RESULT. On error, an error code is returned
and NULL is stored at RESULT. */
gpg_error_t
agent_scd_keyinfo (const char *keygrip, int cap,
keypair_info_t *result)
{
int err;
struct card_keyinfo_parm_s parm;
char line[ASSUAN_LINELENGTH];
char *list_option;
*result = NULL;
switch (cap)
{
case 0: list_option = "--list"; break;
case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break;
case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break;
case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
memset (&parm, 0, sizeof parm);
snprintf (line, sizeof line, "SCD KEYINFO %s",
keygrip ? keygrip : list_option);
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_keyinfo_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_keypair_info (parm.list);
return err;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
* CHVNO 1: Change the PIN
* 2: For v1 cards: Same as 1.
* For v2 cards: Reset the PIN using the Reset Code.
* 3: Change the admin PIN
* 101: Set a new PIN and reset the retry counter
* 102: For v1 cars: Same as 101.
* For v2 cards: Set a new Reset Code.
* SERIALNO is not used.
* Used by:
* card-util.c
*/
int
agent_scd_change_pin (int chvno, const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
const char *reset = "";
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
if (chvno >= 100)
reset = "--reset";
chvno %= 100;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here.
* Used by:
* card-util.c
*/
int
agent_scd_checkpin (const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Note: All strings shall be UTF-8. On success the caller needs to
free the string stored at R_PASSPHRASE. On error NULL will be
stored at R_PASSPHRASE and an appropriate fpf error code
returned. */
gpg_error_t
agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int repeat,
int check,
char **r_passphrase)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *arg1 = NULL;
char *arg2 = NULL;
char *arg3 = NULL;
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_passphrase = NULL;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent understands the repeat option. */
if (assuan_transact (agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE repeat",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (cache_id && *cache_id)
if (!(arg1 = percent_plus_escape (cache_id)))
goto no_mem;
if (err_msg && *err_msg)
if (!(arg2 = percent_plus_escape (err_msg)))
goto no_mem;
if (prompt && *prompt)
if (!(arg3 = percent_plus_escape (prompt)))
goto no_mem;
if (desc_msg && *desc_msg)
if (!(arg4 = percent_plus_escape (desc_msg)))
goto no_mem;
snprintf (line, DIM(line),
"GET_PASSPHRASE --data --repeat=%d%s -- %s %s %s %s",
repeat,
check? " --check --qualitybar":"",
arg1? arg1:"X",
arg2? arg2:"X",
arg3? arg3:"X",
arg4? arg4:"X");
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
init_membuf_secure (&data, 64);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
rc = gpg_error_from_syserror ();
}
return rc;
no_mem:
rc = gpg_error_from_syserror ();
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
return rc;
}
gpg_error_t
agent_clear_passphrase (const char *cache_id)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
if (!cache_id || !*cache_id)
return 0;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
return assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpg_agent_get_confirmation (const char *desc)
{
int rc;
char *tmp;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
tmp = percent_plus_escape (desc);
if (!tmp)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp);
xfree (tmp);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return rc;
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (NULL, 0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
struct keyinfo_data_parm_s
{
char *serialno;
int is_smartcard;
int passphrase_cached;
int cleartext;
int card_available;
};
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
struct keyinfo_data_parm_s *data = opaque;
char *s;
if ((s = has_leading_keyword (line, "KEYINFO")) && data)
{
/* Parse the arguments:
* 0 1 2 3 4 5
* <keygrip> <type> <serialno> <idstr> <cached> <protection>
*
* 6 7 8
* <sshfpr> <ttl> <flags>
*/
char *fields[9];
if (split_fields (s, fields, DIM (fields)) == 9)
{
data->is_smartcard = (fields[1][0] == 'T');
if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-"))
data->serialno = xtrystrdup (fields[2]);
/* '1' for cached */
data->passphrase_cached = (fields[4][0] == '1');
/* 'P' for protected, 'C' for clear */
data->cleartext = (fields[5][0] == 'C');
/* 'A' for card is available */
data->card_available = (fields[8][0] == 'A');
}
}
return 0;
}
/* Ask the agent whether a secret key for the given public key is
available. Returns 0 if not available. Bigger value is preferred. */
int
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *hexgrip;
struct keyinfo_data_parm_s keyinfo;
memset (&keyinfo, 0, sizeof keyinfo);
err = start_agent (ctrl, 0);
if (err)
return err;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
return err;
snprintf (line, sizeof line, "KEYINFO %s", hexgrip);
xfree (hexgrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
xfree (keyinfo.serialno);
if (err)
return 0;
if (keyinfo.card_available)
return 4;
if (keyinfo.passphrase_cached)
return 3;
if (keyinfo.is_smartcard)
return 2;
return 1;
}
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *p;
kbnode_t kbctx, node;
int nkeys;
unsigned char grip[KEYGRIP_LEN];
err = start_agent (ctrl, 0);
if (err)
return err;
err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
found in KEYBLOCK. */
p = stpcpy (line, "HAVEKEY");
for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2))
{
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
break; /* Seckey available or unexpected error - ready. */
p = stpcpy (line, "HAVEKEY");
nkeys = 0;
}
err = keygrip_from_pk (node->pkt->pkt.public_key, grip);
if (err)
return err;
*p++ = ' ';
bin2hex (grip, 20, p);
p += 40;
nkeys++;
}
if (!err && nkeys)
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO.
if r_cleartext is not NULL, the referenced int will be set to 1 if
the agent's copy of the key is stored in the clear, or 0 otherwise
*/
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct keyinfo_data_parm_s keyinfo;
memset (&keyinfo, 0,sizeof keyinfo);
*r_serialno = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
if (!err && keyinfo.serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (keyinfo.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (keyinfo.serialno);
else
{
*r_serialno = keyinfo.serialno;
if (r_cleartext)
*r_cleartext = keyinfo.cleartext;
}
return err;
}
/* Status callback for agent_import_key, agent_export_key and
agent_genkey. */
static gpg_error_t
cache_nonce_status_cb (void *opaque, const char *line)
{
struct cache_nonce_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "CACHE_NONCE")))
{
if (parm->cache_nonce_addr)
{
xfree (*parm->cache_nonce_addr);
*parm->cache_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PASSWD_NONCE")))
{
if (parm->passwd_nonce_addr)
{
xfree (*parm->passwd_nonce_addr);
*parm->passwd_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (opt.enable_progress_filter)
write_status_text (STATUS_PROGRESS, s);
}
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYPARAM"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->keyparms, strlen (parm->keyparms));
}
else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase)
{
err = assuan_send_data (parm->dflt->ctx,
parm->passphrase, strlen (parm->passphrase));
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to generate a new key. KEYPARMS is the usual
S-expression giving the parameters of the key. gpg-agent passes it
gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not
to protect the generated key. If NO_PROTECTION is not set and
PASSPHRASE is not NULL the agent is requested to protect the key
with that passphrase instead of asking for one. */
gpg_error_t
agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, gcry_sexp_t *r_pubkey)
{
gpg_error_t err;
struct genkey_parm_s gk_parm;
struct cache_nonce_parm_s cn_parm;
struct default_inq_parm_s dfltparm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (passwd_nonce_addr && *passwd_nonce_addr)
; /* A RESET would flush the passwd nonce cache. */
else
{
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf (&data, 1024);
gk_parm.dflt = &dfltparm;
gk_parm.keyparms = keyparms;
gk_parm.passphrase = passphrase;
snprintf (line, sizeof line, "GENKEY%s%s%s%s%s",
no_protection? " --no-protection" :
passphrase ? " --inq-passwd" :
/* */ "",
passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_pubkey, NULL, buf, len);
xfree (buf);
}
return err;
}
/* Call the agent to read the public key part for a given keygrip.
* Values from FROMCARD:
* 0 - Standard
* 1 - The key is read from the current card
* via the agent and a stub file is created.
*/
gpg_error_t
agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (fromcard)
snprintf (line, DIM(line), "READKEY --card -- %s", hexkeygrip);
else
snprintf (line, DIM(line), "READKEY -- %s", hexkeygrip);
init_membuf (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. DESC is a description of the key to be
displayed if the agent needs to ask for the PIN. DIGEST and
DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
used to compute the digest. If CACHE_NONCE is used the agent is
advised to first try a passphrase associated with that nonce. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen, int digestalgo,
gcry_sexp_t *r_sigval)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_sigval = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
bin2hex (digest, digestlen, line + strlen (line));
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, sizeof line, "PKSIGN%s%s",
cache_nonce? " -- ":"",
cache_nonce? cache_nonce:"");
if (DBG_CLOCK)
log_clock ("enter signing");
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (DBG_CLOCK)
log_clock ("leave signing");
if (err)
xfree (get_membuf (&data, NULL));
else
{
unsigned char *buf;
size_t len;
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_sigval, NULL, buf, len);
xfree (buf);
}
}
return err;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END. */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->dflt->ctx,
parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Check whether there is any padding info from the agent. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Call the agent to do a decrypt operation using the key identified
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
success the decoded value is stored verbatim at R_BUF and its
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
and PUBKEY_ALGO are used to construct additional promots or status
messages. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t n, len;
char *p, *buf, *endp;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
if (!keygrip || strlen(keygrip) != 40
|| !s_ciphertext || !r_buf || !r_buflen || !r_padding)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
*r_padding = -1;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, sizeof line, "SETKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf_secure (&data, 1024);
{
struct cipher_parm_s parm;
parm.dflt = &dfltparm;
parm.ctx = agent_ctx;
err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
if (err)
return err;
err = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &parm,
padding_info_cb, r_padding);
xfree (parm.ciphertext);
}
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (len == 0 || *buf != '(')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
while (buf[len-1] == 0)
len--;
if (buf[len-1] != ')')
return gpg_error (GPG_ERR_INV_SEXP);
len--; /* Drop the final close-paren. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
len -= 8; /* Count only the data of the second part. */
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
endp++;
if (endp-p+n > len)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
}
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be used for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_kek = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
const void *key, size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct import_key_parm_s parm;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
parm.dflt = &dfltparm;
parm.key = key;
parm.keylen = keylen;
snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s",
unattended? " --unattended":"",
force? " --force":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
inq_import_key_parms, &parm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Receive a secret key from the agent. HEXKEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). if OPENPGP_PROTECTED
is not zero, ensure that the key material is returned in RFC
4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is
not NULL the agent is advised to first try a passphrase associated
with that nonce. On success the key is stored as a canonical
S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int openpgp_protected, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_result = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s",
openpgp_protected ? "--openpgp ":"",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
init_membuf_secure (&data, 1024);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
/* Status callback for handling confirmation. */
static gpg_error_t
confirm_status_cb (void *opaque, const char *line)
{
struct confirm_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "SETDESC")))
{
xfree (parm->desc);
parm->desc = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETOK")))
{
xfree (parm->ok);
parm->ok = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETNOTOK")))
{
xfree (parm->notok);
parm->notok = unescape_status_string (s);
}
return 0;
}
/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC
is not NULL, display DESC instead of the default description
message. If FORCE is true the agent is advised not to ask for
confirmation. */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int force)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
struct confirm_parm_s confirm_parm;
memset (&confirm_parm, 0, sizeof confirm_parm);
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.confirm = &confirm_parm;
err = start_agent (ctrl, 0);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "DELETE_KEY%s %s",
force? " --force":"", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
confirm_status_cb, &confirm_parm);
xfree (confirm_parm.desc);
xfree (confirm_parm.ok);
xfree (confirm_parm.notok);
return err;
}
/* Ask the agent to change the passphrase of the key identified by
* HEXKEYGRIP. If DESC is not NULL, display DESC instead of the
* default description message. If CACHE_NONCE_ADDR is not NULL the
* agent is advised to first try a passphrase associated with that
* nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use
* the passphrase associated with that nonce for the new passphrase.
* If VERIFY is true the passphrase is only verified. */
gpg_error_t
agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
if (verify)
snprintf (line, DIM(line), "PASSWD %s%s --verify %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
else
snprintf (line, DIM(line), "PASSWD %s%s %s%s %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
hexkeygrip);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = passwd_nonce_addr;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Return the version reported by gpg-agent. */
gpg_error_t
agent_get_version (ctrl_t ctrl, char **r_version)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
return err;
err = get_assuan_server_version (agent_ctx, 0, r_version);
return err;
}
diff --git a/g10/call-agent.h b/g10/call-agent.h
index b8199db15..8df5ff5a3 100644
--- a/g10/call-agent.h
+++ b/g10/call-agent.h
@@ -1,241 +1,241 @@
/* call-agent.h - Divert operations to the agent
* Copyright (C) 2003 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_G10_CALL_AGENT_H
#define GNUPG_G10_CALL_AGENT_H
struct key_attr {
int algo; /* Algorithm identifier. */
union {
unsigned int nbits; /* Supported keysize. */
const char *curve; /* Name of curve. */
};
};
struct agent_card_info_s
{
int error; /* private. */
char *reader; /* Reader information. */
char *apptype; /* Malloced application type string. */
char *serialno; /* malloced hex string. */
char *disp_name; /* malloced. */
char *disp_lang; /* malloced. */
int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */
char *pubkey_url; /* malloced. */
char *login_data; /* malloced. */
char *private_do[4]; /* malloced. */
char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */
char cafpr2len;
char cafpr3len;
char cafpr1[20];
char cafpr2[20];
char cafpr3[20];
unsigned char fpr1len; /* Length of the fingerprint or 0 if invalid. */
unsigned char fpr2len;
unsigned char fpr3len;
char fpr1[20];
char fpr2[20];
char fpr3[20];
u32 fpr1time;
u32 fpr2time;
u32 fpr3time;
char grp1[20]; /* The keygrip for OPENPGP.1 */
char grp2[20]; /* The keygrip for OPENPGP.2 */
char grp3[20]; /* The keygrip for OPENPGP.3 */
unsigned long sig_counter;
int chv1_cached; /* True if a PIN is not required for each
signing. Note that the gpg-agent might cache
it anyway. */
int is_v2; /* True if this is a v2 card. */
int chvmaxlen[3]; /* Maximum allowed length of a CHV. */
int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */
struct key_attr key_attr[3];
struct {
unsigned int ki:1; /* Key import available. */
unsigned int aac:1; /* Algorithm attributes are changeable. */
unsigned int kdf:1; /* KDF object to support PIN hashing available. */
unsigned int bt:1; /* Button for confirmation available. */
} extcap;
unsigned int status_indicator;
int kdf_do_enabled; /* Non-zero if card has a KDF object, 0 if not. */
int uif[3]; /* True if User Interaction Flag is on. */
};
/* Object to store information from the KEYPAIRINFO or the KEYINFO
* status lines. */
struct keypair_info_s
{
struct keypair_info_s *next;
char keygrip[2 * KEYGRIP_LEN + 1]; /* Stored in hex. */
char *serialno; /* NULL or the malloced serialno. */
char *idstr; /* Malloced keyref (e.g. "OPENPGP.1") */
unsigned int usage; /* Key usage flags. */
u32 keytime; /* Key creation time from the card's DO. */
int algo; /* Helper to store the pubkey algo. */
};
typedef struct keypair_info_s *keypair_info_t;
/* Release the card info structure. */
void agent_release_card_info (struct agent_card_info_s *info);
/* Return card info. */
int agent_scd_learn (struct agent_card_info_s *info, int force);
/* Get the keypariinfo directly from scdaemon. */
gpg_error_t agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref,
keypair_info_t *r_list);
/* Return list of cards. */
int agent_scd_cardlist (strlist_t *result);
/* Free a keypair info list. */
void free_keypair_info (keypair_info_t l);
/* Return card key information. */
gpg_error_t agent_scd_keyinfo (const char *keygrip, int cap,
keypair_info_t *result);
/* Return the serial number, possibly select by DEMAND. */
int agent_scd_serialno (char **r_serialno, const char *demand);
/* Send an APDU to the card. */
gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw);
/* Get attribute NAME from the card and store at R_VALUE. */
gpg_error_t agent_scd_getattr_one (const char *name, char **r_value);
/* Update INFO with the attribute NAME. */
int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
/* Send the KEYTOCARD command. */
int agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp);
/* Send a SETATTR command to the SCdaemon. */
gpg_error_t agent_scd_setattr (const char *name,
const void *value, size_t valuelen);
/* Send a WRITECERT command to the SCdaemon. */
int agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen);
/* Send a GENKEY command to the SCdaemon. */
int agent_scd_genkey (int keyno, int force, u32 *createtime);
/* Send a READCERT command to the SCdaemon. */
int agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen);
/* Send a READKEY command to the SCdaemon. */
gpg_error_t agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr,
gcry_sexp_t *r_result, u32 *r_keytime);
/* Change the PIN of an OpenPGP card or reset the retry counter. */
int agent_scd_change_pin (int chvno, const char *serialno);
/* Send the CHECKPIN command to the SCdaemon. */
int agent_scd_checkpin (const char *serialno);
/* Send the GET_PASSPHRASE command to the agent. */
gpg_error_t agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int repeat,
int check,
char **r_passphrase);
/* Send the CLEAR_PASSPHRASE command to the agent. */
gpg_error_t agent_clear_passphrase (const char *cache_id);
/* Present the prompt DESC and ask the user to confirm. */
gpg_error_t gpg_agent_get_confirmation (const char *desc);
/* Return the S2K iteration count as computed by gpg-agent. */
unsigned long agent_get_s2k_count (void);
/* Check whether a secret key for public key PK is available. Returns
0 if not available, positive value if the secret key is available. */
int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk);
-/* Ask the agent whether a secret key is availabale for any of the
+/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock);
/* Return infos about the secret key with HEXKEYGRIP. */
gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext);
/* Generate a new key. */
gpg_error_t agent_genkey (ctrl_t ctrl,
char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase,
gcry_sexp_t *r_pubkey);
/* Read a public key. FROMCARD may be 0, 1, or 2. */
gpg_error_t agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey);
/* Create a signature. */
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *hexkeygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen,
int digestalgo,
gcry_sexp_t *r_sigval);
/* Decrypt a ciphertext. */
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen,
int *r_padding);
/* Retrieve a key encryption key. */
gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen);
/* Send a key to the agent. */
gpg_error_t agent_import_key (ctrl_t ctrl, const char *desc,
char **cache_nonce_addr, const void *key,
size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo);
/* Receive a key from the agent. */
gpg_error_t agent_export_key (ctrl_t ctrl, const char *keygrip,
const char *desc, int openpgp_protected,
char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo);
/* Delete a key from the agent. */
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *hexkeygrip,
const char *desc, int force);
/* Change the passphrase of a key. */
gpg_error_t agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int verify,
char **cache_nonce_addr, char **passwd_nonce_addr);
/* Get the version reported by gpg-agent. */
gpg_error_t agent_get_version (ctrl_t ctrl, char **r_version);
#endif /*GNUPG_G10_CALL_AGENT_H*/
diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
index abcc82500..d7d9e2da4 100644
--- a/g10/call-keyboxd.c
+++ b/g10/call-keyboxd.c
@@ -1,1234 +1,1234 @@
/* call-keyboxd.c - Access to the keyboxd storage server
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include <npth.h>
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/host2net.h"
#include "../common/exechelp.h"
#include "../common/status.h"
#include "keydb.h"
#include "keydb-private.h" /* For struct keydb_handle_s */
/* Data used to keep track of keybox daemon sessions. This allows us
* to use several sessions with the keyboxd and also to re-use already
* established sessions. Note that gpg.h defines the type
* keyboxd_local_t for this structure. */
struct keyboxd_local_s
{
/* Link to other keyboxd contexts which are used simultaneously. */
struct keyboxd_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* This object is used if fd-passing is used to convey the
* keyblocks. */
struct {
/* NULL or a stream used to receive data. */
estream_t fp;
/* Condition variable to sync the datastream with the command. */
npth_mutex_t mutex;
npth_cond_t cond;
/* The found keyblock or the parsing error. */
kbnode_t found_keyblock;
gpg_error_t found_err;
} datastream;
/* I/O buffer with the last search result or NULL. Used if
* D-lines are used to convey the keyblocks. */
iobuf_t search_result;
/* This flag set while an operation is running on this context. */
unsigned int is_active : 1;
/* This flag is set to record that the standard per session init has
* been done. */
unsigned int per_session_init_done : 1;
/* Flag indicating that a search reset is required. */
unsigned int need_search_reset : 1;
};
/* Local prototypes. */
static void *datastream_thread (void *arg);
static void
lock_datastream (keyboxd_local_t kbl)
{
int rc = npth_mutex_lock (&kbl->datastream.mutex);
if (rc)
log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_errno (rc)));
}
static void
unlock_datastream (keyboxd_local_t kbl)
{
int rc = npth_mutex_unlock (&kbl->datastream.mutex);
if (rc)
log_fatal ("%s: failed to release mutex: %s\n", __func__,
gpg_strerror (gpg_error_from_errno (rc)));
}
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpg_keyboxd_deinit_session_data (ctrl_t ctrl)
{
keyboxd_local_t kbl;
while ((kbl = ctrl->keyboxd_local))
{
ctrl->keyboxd_local = kbl->next;
if (kbl->is_active)
log_error ("oops: trying to cleanup an active keyboxd context\n");
else
{
es_fclose (kbl->datastream.fp);
kbl->datastream.fp = NULL;
assuan_release (kbl->ctx);
kbl->ctx = NULL;
}
xfree (kbl);
}
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, 0, &serverversion);
if (err)
log_error (_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
" ", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Connect to the keybox daemon and launch it if necessary. Handle
* the server's initial greeting and set global options. Returns a
* new assuan context or an error. */
static gpg_error_t
create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_keyboxd (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.keyboxd_program,
opt.autostart, opt.verbose, DBG_IPC,
NULL, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no keyboxd running in this session\n"));
}
}
else if (!err && !(err = warn_version_mismatch (ctx, KEYBOXD_NAME)))
{
/* Place to emit global options. */
}
if (err)
assuan_release (ctx);
else
*r_ctx = ctx;
return err;
}
/* Setup the pipe used for receiving data from the keyboxd. Store the
* info on KBL. */
static gpg_error_t
prepare_data_pipe (keyboxd_local_t kbl)
{
gpg_error_t err;
int rc;
int inpipe[2];
estream_t infp;
npth_t thread;
npth_attr_t tattr;
err = gnupg_create_inbound_pipe (inpipe, &infp, 0);
if (err)
{
log_error ("error creating inbound pipe: %s\n", gpg_strerror (err));
return err; /* That should not happen. */
}
err = assuan_sendfd (kbl->ctx, INT2FD (inpipe[1]));
if (err)
{
log_error ("sending sending fd %d to keyboxd: %s <%s>\n",
inpipe[1], gpg_strerror (err), gpg_strsource (err));
es_fclose (infp);
close (inpipe[1]);
return 0; /* Server may not support fd-passing. */
}
err = assuan_transact (kbl->ctx, "OUTPUT FD",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
log_info ("keyboxd does not accept our fd: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
es_fclose (infp);
return 0;
}
kbl->datastream.fp = infp;
kbl->datastream.found_keyblock = NULL;
kbl->datastream.found_err = 0;
rc = npth_attr_init (&tattr);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error preparing thread for keyboxd: %s\n",gpg_strerror (err));
es_fclose (infp);
kbl->datastream.fp = NULL;
return err;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
rc = npth_create (&thread, &tattr, datastream_thread, kbl);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error spawning thread for keyboxd: %s\n", gpg_strerror (err));
npth_attr_destroy (&tattr);
es_fclose (infp);
kbl->datastream.fp = NULL;
return err;
}
return 0;
}
/* Get a context for accessing keyboxd. If no context is available a
* new one is created and if necessary keyboxd is started. R_KBL
* receives a pointer to the local context object. */
static gpg_error_t
open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl)
{
gpg_error_t err;
int rc;
keyboxd_local_t kbl;
*r_kbl = NULL;
for (;;)
{
for (kbl = ctrl->keyboxd_local; kbl && kbl->is_active; kbl = kbl->next)
;
if (kbl)
{
/* Found an inactive keyboxd session - return that. */
log_assert (!kbl->is_active);
/* But first do the per session init if not yet done. */
if (!kbl->per_session_init_done)
{
err = prepare_data_pipe (kbl);
if (err)
return err;
kbl->per_session_init_done = 1;
}
kbl->is_active = 1;
kbl->need_search_reset = 1;
*r_kbl = kbl;
return 0;
}
/* None found. Create a new session and retry. */
kbl = xtrycalloc (1, sizeof *kbl);
if (!kbl)
return gpg_error_from_syserror ();
rc = npth_mutex_init (&kbl->datastream.mutex, NULL);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
xfree (kbl);
return err;
}
rc = npth_cond_init (&kbl->datastream.cond, NULL);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("error initializing condition: %s\n", gpg_strerror (err));
npth_mutex_destroy (&kbl->datastream.mutex);
xfree (kbl);
return err;
}
err = create_new_context (ctrl, &kbl->ctx);
if (err)
{
npth_cond_destroy (&kbl->datastream.cond);
npth_mutex_destroy (&kbl->datastream.mutex);
xfree (kbl);
return err;
}
/* For thread-saftey we add it to the list and retry; this is
* easier than to employ a lock. */
kbl->next = ctrl->keyboxd_local;
ctrl->keyboxd_local = kbl;
}
/*NOTREACHED*/
}
/* Create a new database handle. A database handle is similar to a
* file handle: it contains a local file position. This is used when
* searching: subsequent searches resume where the previous search
* left off. To rewind the position, use keydb_search_reset(). This
* function returns NULL on error, sets ERRNO, and prints an error
* diagnostic. Depending on --use-keyboxd either the old internal
* keydb code is used (keydb.c) or, if set, the processing is diverted
* to the keyboxd. */
/* FIXME: We should change the interface to return a gpg_error_t. */
KEYDB_HANDLE
keydb_new (ctrl_t ctrl)
{
gpg_error_t err;
KEYDB_HANDLE hd;
if (DBG_CLOCK)
log_clock ("keydb_new");
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!opt.use_keyboxd)
{
err = internal_keydb_init (hd);
goto leave;
}
hd->use_keyboxd = 1;
hd->ctrl = ctrl;
err = open_context (ctrl, &hd->kbl);
leave:
if (err)
{
int rc;
log_error (_("error opening key DB: %s\n"), gpg_strerror (err));
xfree (hd);
hd = NULL;
if (!(rc = gpg_err_code_to_errno (err)))
rc = gpg_err_code_to_errno (GPG_ERR_EIO);
gpg_err_set_errno (rc);
}
return hd;
}
/* Release a keydb handle. */
void
keydb_release (KEYDB_HANDLE hd)
{
keyboxd_local_t kbl;
if (!hd)
return;
if (DBG_CLOCK)
log_clock ("keydb_release");
if (!hd->use_keyboxd)
internal_keydb_deinit (hd);
else
{
kbl = hd->kbl;
if (DBG_CLOCK)
log_clock ("close_context (found)");
if (!kbl->is_active)
log_fatal ("closing inactive keyboxd context %p\n", kbl);
kbl->is_active = 0;
hd->kbl = NULL;
hd->ctrl = NULL;
}
xfree (hd);
}
/* Take a lock if we are not using the keyboxd. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
return internal_keydb_lock (hd);
return 0;
}
/* FIXME: This helper is duplicates code of partse_keyblock_image. */
static gpg_error_t
keydb_get_keyblock_do_parse (iobuf_t iobuf, int pk_no, int uid_no,
kbnode_t *r_keyblock)
{
gpg_error_t err;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t keyblock = NULL;
kbnode_t node, *tail;
int in_cert, save_mode;
int pk_count, uid_count;
*r_keyblock = NULL;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
return gpg_error_from_syserror ();
init_packet (pkt);
init_parse_packet (&parsectx, iobuf);
save_mode = set_packet_list_mode (0);
in_cert = 0;
tail = NULL;
pk_count = uid_count = 0;
while ((err = parse_packet (&parsectx, pkt)) != -1)
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (err)
{
es_fflush (es_stdout);
log_error ("parse_keyblock_image: read error: %s\n",
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_INV_PACKET)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
case PKT_RING_TRUST:
break; /* Allowed per RFC. */
default:
log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype);
free_packet(pkt, &parsectx);
init_packet(pkt);
continue;
}
/* Other sanity checks. */
if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
{
log_error ("parse_keyblock_image: first packet in a keybox blob "
"is not a public key packet\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
log_error ("parse_keyblock_image: "
"multiple keyblocks in a keybox blob\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
in_cert = 1;
node = new_kbnode (pkt);
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_count == pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_count == uid_no)
node->flag |= 2;
break;
default:
break;
}
if (!keyblock)
keyblock = node;
else
*tail = node;
tail = &node->next;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
break;
}
init_packet (pkt);
}
set_packet_list_mode (save_mode);
if (err == -1 && keyblock)
err = 0; /* Got the entire keyblock. */
if (err)
release_kbnode (keyblock);
else
{
*r_keyblock = keyblock;
}
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
return err;
}
/* The thread used to read from the data stream. This is running as
* long as the connection and its datastream exists. */
static void *
datastream_thread (void *arg)
{
keyboxd_local_t kbl = arg;
gpg_error_t err;
int rc;
unsigned char lenbuf[4];
size_t nread, datalen;
iobuf_t iobuf;
int pk_no, uid_no;
kbnode_t keyblock, tmpkeyblock;
log_debug ("Datastream_thread started\n");
while (kbl->datastream.fp)
{
/* log_debug ("Datastream_thread waiting ...\n"); */
if (es_read (kbl->datastream.fp, lenbuf, 4, &nread))
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
continue;
log_error ("error reading data length from keyboxd: %s\n",
gpg_strerror (err));
gnupg_sleep (1);
continue;
}
if (nread != 4)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error reading data length from keyboxd: %s\n",
"short read");
continue;
}
datalen = buf32_to_size_t (lenbuf);
/* log_debug ("keyboxd announced %zu bytes\n", datalen); */
iobuf = iobuf_esopen (kbl->datastream.fp, "rb", 1, datalen);
pk_no = uid_no = 0; /* FIXME: Get this from the keyboxd. */
err = keydb_get_keyblock_do_parse (iobuf, pk_no, uid_no, &keyblock);
iobuf_close (iobuf);
if (!err)
{
/* log_debug ("parsing datastream succeeded\n"); */
/* Thread-safe assignment to the result var: */
tmpkeyblock = kbl->datastream.found_keyblock;
kbl->datastream.found_keyblock = keyblock;
release_kbnode (tmpkeyblock);
}
else
{
/* log_debug ("parsing datastream failed: %s <%s>\n", */
/* gpg_strerror (err), gpg_strsource (err)); */
tmpkeyblock = kbl->datastream.found_keyblock;
kbl->datastream.found_keyblock = NULL;
kbl->datastream.found_err = err;
release_kbnode (tmpkeyblock);
}
/* Tell the main thread. */
lock_datastream (kbl);
rc = npth_cond_signal (&kbl->datastream.cond);
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("%s: signaling condition failed: %s\n",
__func__, gpg_strerror (err));
}
unlock_datastream (kbl);
}
log_debug ("Datastream_thread finished\n");
return NULL;
}
/* Return the keyblock last found by keydb_search() in *RET_KB.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
* code.
*
* The returned keyblock has the kbnode flag bit 0 set for the node
* with the public key used to locate the keyblock or flag bit 1 set
* for the user ID node. */
gpg_error_t
keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb)
{
gpg_error_t err;
int pk_no, uid_no;
*ret_kb = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("%s enter", __func__);
if (!hd->use_keyboxd)
{
err = internal_keydb_get_keyblock (hd, ret_kb);
goto leave;
}
if (hd->kbl->search_result)
{
pk_no = uid_no = 0; /*FIXME: Get this from the keyboxd. */
err = keydb_get_keyblock_do_parse (hd->kbl->search_result,
pk_no, uid_no, ret_kb);
/* In contrast to the old code we close the iobuf here and thus
* this function may be called only once to get a keyblock. */
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
else if (hd->kbl->datastream.found_keyblock)
{
*ret_kb = hd->kbl->datastream.found_keyblock;
hd->kbl->datastream.found_keyblock = NULL;
err = 0;
}
else
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
leave:
if (DBG_CLOCK)
log_clock ("%s leave%s", __func__, err? " (failed)":"");
return err;
}
/* Communication object for STORE commands. */
struct store_parm_s
{
assuan_context_t ctx;
const void *data; /* The key in OpenPGP binary format. */
size_t datalen; /* The length of DATA. */
};
/* Handle the inquiries from the STORE command. */
static gpg_error_t
store_inq_cb (void *opaque, const char *line)
{
struct store_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "BLOB"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
*
* This doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. Note:
* if there isn't a keyblock in the keyring corresponding to KB, then
* this function returns GPG_ERR_VALUE_NOT_FOUND.
*
* This function selects the matching record and modifies the current
* file position to point to the record just after the selected entry.
* Thus, if you do a subsequent search using HD, you should first do a
* keydb_search_reset. Further, if the selected record is important,
* you should use keydb_push_found_state and keydb_pop_found_state to
* save and restore it. */
gpg_error_t
keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
iobuf_t iobuf = NULL;
struct store_parm_s parm = {NULL};
log_assert (kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_update_keyblock (ctrl, hd, kb);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
err = build_keyblock_image (kb, &iobuf);
if (err)
goto leave;
parm.ctx = hd->kbl->ctx;
parm.data = iobuf_get_temp_buffer (iobuf);
parm.datalen = iobuf_get_temp_length (iobuf);
err = assuan_transact (hd->kbl->ctx, "STORE --update",
NULL, NULL,
store_inq_cb, &parm,
NULL, NULL);
leave:
iobuf_close (iobuf);
return err;
}
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
*
* By default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
* keydb_search_reset was called), then the keyring / keybox where the
* next search would start is used (i.e., the current file position).
* In keyboxd mode the keyboxd decides where to store it.
*
* Note: this doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
iobuf_t iobuf = NULL;
struct store_parm_s parm = {NULL};
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_insert_keyblock (hd, kb);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
err = build_keyblock_image (kb, &iobuf);
if (err)
goto leave;
parm.ctx = hd->kbl->ctx;
parm.data = iobuf_get_temp_buffer (iobuf);
parm.datalen = iobuf_get_temp_length (iobuf);
err = assuan_transact (hd->kbl->ctx, "STORE --insert",
NULL, NULL,
store_inq_cb, &parm,
NULL, NULL);
leave:
iobuf_close (iobuf);
return err;
}
/* Delete the currently selected keyblock. If you haven't done a
* search yet on this database handle (or called keydb_search_reset),
* then this function returns an error.
*
- * Returns 0 on success or an error code, if an error occured. */
+ * Returns 0 on success or an error code, if an error occurred. */
gpg_error_t
keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t err;
unsigned char hexubid[UBID_LEN * 2 + 1];
char line[ASSUAN_LINELENGTH];
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_delete_keyblock (hd);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
if (!hd->last_ubid_valid)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
bin2hex (hd->last_ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "DELETE %s", hexubid);
err = assuan_transact (hd->kbl->ctx, line,
NULL, NULL,
NULL, NULL,
NULL, NULL);
leave:
return err;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database.
*
* Returns 0 on success and an error code if an error occurred. */
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("%s", __func__);
if (DBG_CACHE)
log_debug ("%s (hd=%p)", __func__, hd);
if (!hd->use_keyboxd)
{
err = internal_keydb_search_reset (hd);
goto leave;
}
/* All we need is to tell search that a reset is pending. Note that
* keydb_new sets this flag as well. To comply with the
* specification of keydb_delete_keyblock we also need to clear the
* ubid flag so that after a reset a delete can't be performed. */
hd->kbl->need_search_reset = 1;
hd->last_ubid_valid = 0;
err = 0;
leave:
return err;
}
/* Status callback for SEARCH and NEXT operaions. */
static gpg_error_t
search_status_cb (void *opaque, const char *line)
{
KEYDB_HANDLE hd = opaque;
gpg_error_t err = 0;
const char *s;
if ((s = has_leading_keyword (line, "PUBKEY_INFO")))
{
if (atoi (s) != PUBKEY_TYPE_OPGP)
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
else
{
hd->last_ubid_valid = 0;
while (*s && !spacep (s))
s++;
if (hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid))
hd->last_ubid_valid = 1;
else
err = gpg_error (GPG_ERR_INV_VALUE);
}
}
return err;
}
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, returns
* GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
* occurred, returns an error code.
*
* The returned key is considered to be selected and the raw data can,
* for instance, be returned by calling keydb_get_keyblock(). */
gpg_error_t
keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex)
{
gpg_error_t err;
int i;
char line[ASSUAN_LINELENGTH];
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (descindex)
*descindex = 0; /* Make sure it is always set on return. */
if (DBG_CLOCK)
log_clock ("%s enter", __func__);
if (DBG_LOOKUP)
{
log_debug ("%s: %zd search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (!hd->use_keyboxd)
{
err = internal_keydb_search (hd, desc, ndesc, descindex);
goto leave;
}
/* Clear the result objects. */
if (hd->kbl->search_result)
{
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
if (hd->kbl->datastream.found_keyblock)
{
release_kbnode (hd->kbl->datastream.found_keyblock);
hd->kbl->datastream.found_keyblock = NULL;
}
/* Check whether this is a NEXT search. */
if (!hd->kbl->need_search_reset)
{
/* No reset requested thus continue the search. The keyboxd
* keeps the context of the search and thus the NEXT operates on
* the last search pattern. This is how we always used the
* keydb.c functions. In theory we were able to modify the
* search pattern between searches but that is not anymore
* supported by keyboxd and a cursory check does not show that
* we actually made used of that misfeature. */
snprintf (line, sizeof line, "NEXT");
goto do_search;
}
hd->kbl->need_search_reset = 0;
if (!ndesc)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* FIXME: Implement --multi */
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
snprintf (line, sizeof line, "SEARCH =%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
snprintf (line, sizeof line, "SEARCH *%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
snprintf (line, sizeof line, "SEARCH <%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAILSUB:
snprintf (line, sizeof line, "SEARCH @%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
snprintf (line, sizeof line, "SEARCH .%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_WORDS:
snprintf (line, sizeof line, "SEARCH +%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (line, sizeof line, "SEARCH 0x%08lX",
(ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (line, sizeof line, "SEARCH 0x%08lX%08lX",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
{
unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN);
bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr);
snprintf (line, sizeof line, "SEARCH 0x%s", hexfpr);
}
break;
case KEYDB_SEARCH_MODE_ISSUER:
snprintf (line, sizeof line, "SEARCH #/%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
case KEYDB_SEARCH_MODE_SN:
snprintf (line, sizeof line, "SEARCH #%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBJECT:
snprintf (line, sizeof line, "SEARCH /%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
{
unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip);
snprintf (line, sizeof line, "SEARCH &%s", hexgrip);
}
break;
case KEYDB_SEARCH_MODE_UBID:
{
unsigned char hexubid[UBID_LEN * 2 + 1];
bin2hex (desc[0].u.ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "SEARCH ^%s", hexubid);
}
break;
case KEYDB_SEARCH_MODE_FIRST:
snprintf (line, sizeof line, "SEARCH");
break;
case KEYDB_SEARCH_MODE_NEXT:
log_debug ("%s: mode next - we should not get to here!\n", __func__);
snprintf (line, sizeof line, "NEXT");
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_search:
hd->last_ubid_valid = 0;
if (hd->kbl->datastream.fp)
{
/* log_debug ("Sending command '%s'\n", line); */
err = assuan_transact (hd->kbl->ctx, line,
NULL, NULL,
NULL, NULL,
search_status_cb, hd);
if (err)
{
/* log_debug ("Finished command with error: %s\n", gpg_strerror (err)); */
/* Fixme: On unexpected errors we need a way to cancel the
* data stream. Probably it will be best to close and
* reopen it. */
}
else
{
int rc;
/* log_debug ("Finished command .. telling data stream\n"); */
lock_datastream (hd->kbl);
if (!hd->kbl->datastream.found_keyblock)
{
/* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */
rc = npth_cond_wait (&hd->kbl->datastream.cond,
&hd->kbl->datastream.mutex);
/* log_debug ("%s: waiting on datastream.cond done\n", __func__); */
if (rc)
{
err = gpg_error_from_errno (rc);
log_error ("%s: waiting on condition failed: %s\n",
__func__, gpg_strerror (err));
}
}
unlock_datastream (hd->kbl);
}
}
else /* Slower D-line version if fd-passing was not successful. */
{
membuf_t data;
void *buffer;
size_t len;
init_membuf (&data, 8192);
err = assuan_transact (hd->kbl->ctx, line,
put_membuf_cb, &data,
NULL, NULL,
search_status_cb, hd);
if (err)
{
xfree (get_membuf (&data, &len));
goto leave;
}
buffer = get_membuf (&data, &len);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
hd->kbl->search_result = iobuf_temp_with_content (buffer, len);
xfree (buffer);
}
/* if (hd->last_ubid_valid) */
/* log_printhex (hd->last_ubid, 20, "found UBID:"); */
leave:
if (DBG_CLOCK)
log_clock ("%s leave (%sfound)", __func__, err? "not ":"");
return err;
}
diff --git a/g10/export.c b/g10/export.c
index 9160680d7..052e16717 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -1,2447 +1,2447 @@
/* export.c - Export keys in the OpenPGP defined format.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/membuf.h"
#include "../common/host2net.h"
#include "../common/zb32.h"
#include "../common/recsel.h"
#include "../common/mbox-util.h"
#include "../common/init.h"
#include "trustdb.h"
#include "call-agent.h"
#include "key-clean.h"
/* An object to keep track of subkeys. */
struct subkey_list_s
{
struct subkey_list_s *next;
u32 kid[2];
};
typedef struct subkey_list_s *subkey_list_t;
/* An object to track statistics for export operations. */
struct export_stats_s
{
ulong count; /* Number of processed keys. */
ulong secret_count; /* Number of secret keys seen. */
ulong exported; /* Number of actual exported keys. */
};
/* A global variable to store the selector created from
* --export-filter keep-uid=EXPR.
* --export-filter drop-subkey=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now.
*/
static recsel_expr_t export_keep_uid;
static recsel_expr_t export_drop_subkey;
/* Local prototypes. */
static int do_export (ctrl_t ctrl, strlist_t users, int secret,
unsigned int options, export_stats_t stats);
static int do_export_stream (ctrl_t ctrl, iobuf_t out,
strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any);
static gpg_error_t print_pka_or_dane_records
/**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane);
static void
cleanup_export_globals (void)
{
recsel_release (export_keep_uid);
export_keep_uid = NULL;
recsel_release (export_drop_subkey);
export_drop_subkey = NULL;
}
/* Option parser for export options. See parse_options for
details. */
int
parse_export_options(char *str,unsigned int *options,int noisy)
{
struct parse_options export_opts[]=
{
{"export-local-sigs",EXPORT_LOCAL_SIGS,NULL,
N_("export signatures that are marked as local-only")},
{"export-attributes",EXPORT_ATTRIBUTES,NULL,
N_("export attribute user IDs (generally photo IDs)")},
{"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,
N_("export revocation keys marked as \"sensitive\"")},
{"export-clean",EXPORT_CLEAN,NULL,
N_("remove unusable parts from key during export")},
{"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL,
N_("remove as much as possible from key during export")},
{"export-drop-uids", EXPORT_DROP_UIDS, NULL,
N_("Do not export user id or attribute packets")},
{"export-pka", EXPORT_PKA_FORMAT, NULL, NULL },
{"export-dane", EXPORT_DANE_FORMAT, NULL, NULL },
{"backup", EXPORT_BACKUP, NULL,
N_("use the GnuPG key backup format")},
{"export-backup", EXPORT_BACKUP, NULL, NULL },
/* Aliases for backward compatibility */
{"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL},
{"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL},
{"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL},
/* dummy */
{"export-unusable-sigs",0,NULL,NULL},
{"export-clean-sigs",0,NULL,NULL},
{"export-clean-uids",0,NULL,NULL},
{NULL,0,NULL,NULL}
/* add tags for include revoked and disabled? */
};
int rc;
rc = parse_options (str, options, export_opts, noisy);
if (!rc)
return 0;
/* Alter other options we want or don't want for restore. */
if ((*options & EXPORT_BACKUP))
{
*options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES
| EXPORT_SENSITIVE_REVKEYS);
*options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL
| EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT);
}
/* Dropping uids also means to drop attributes. */
if ((*options & EXPORT_DROP_UIDS))
*options &= ~(EXPORT_ATTRIBUTES);
return rc;
}
/* Parse and set an export filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is called several
* times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be exported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*
* - drop-subkey :: If the expression evaluates to true for a subkey
* packet that subkey and all it dependencies will be
* remove from the keyblock. The expression may use these
* variables:
*
* - secret :: 1 for a secret subkey, else 0.
* - key_algo :: Public key algorithm id
*/
gpg_error_t
parse_and_set_export_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_export_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&export_keep_uid, string+9);
else if (!strncmp (string, "drop-subkey=", 12))
err = recsel_parse_expr (&export_drop_subkey, string+12);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Create a new export stats object initialized to zero. On error
returns NULL and sets ERRNO. */
export_stats_t
export_new_stats (void)
{
export_stats_t stats;
return xtrycalloc (1, sizeof *stats);
}
/* Release an export stats object. */
void
export_release_stats (export_stats_t stats)
{
xfree (stats);
}
/* Print export statistics using the status interface. */
void
export_print_stats (export_stats_t stats)
{
if (!stats)
return;
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf, "%lu %lu %lu",
stats->count,
stats->secret_count,
stats->exported );
write_status_text (STATUS_EXPORT_RES, buf);
}
}
/*
* Export public keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all keys will be exported. STATS is
* either an export stats object for update or NULL.
*
* This function is the core of "gpg --export".
*/
int
export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 0, options, stats);
}
/*
* Export secret keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all secret keys will be exported.
* STATS is either an export stats object for update or NULL.
*
* This function is the core of "gpg --export-secret-keys".
*/
int
export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 1, options, stats);
}
/*
* Export secret sub keys (to stdout or to --output FILE).
*
* This is the same as export_seckeys but replaces the primary key by
* a stub key. Depending on opt.armor the output is armored. OPTIONS
* are defined in main.h. If USERS is NULL, all secret subkeys will
* be exported. STATS is either an export stats object for update or
* NULL.
*
* This function is the core of "gpg --export-secret-subkeys".
*/
int
export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 2, options, stats);
}
/*
* Export a single key into a memory buffer. STATS is either an
* export stats object for update or NULL.
*/
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
gpg_error_t err;
iobuf_t iobuf;
int any;
strlist_t helplist;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
helplist = NULL;
if (!add_to_strlist_try (&helplist, keyspec))
return gpg_error_from_syserror ();
iobuf = iobuf_temp ();
err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options,
stats, &any);
if (!err && !any)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err)
{
const void *src;
size_t datalen;
iobuf_flush_temp (iobuf);
src = iobuf_get_temp_buffer (iobuf);
datalen = iobuf_get_temp_length (iobuf);
if (!datalen)
err = gpg_error (GPG_ERR_NO_PUBKEY);
else if (!(*r_data = xtrymalloc (datalen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*r_data, src, datalen);
*r_datalen = datalen;
}
}
iobuf_close (iobuf);
free_strlist (helplist);
if (err && *r_keyblock)
{
release_kbnode (*r_keyblock);
*r_keyblock = NULL;
}
return err;
}
/* Export the keys identified by the list of strings in USERS. If
Secret is false public keys will be exported. With secret true
secret keys will be exported; in this case 1 means the entire
secret keyblock and 2 only the subkeys. OPTIONS are the export
options to apply. */
static int
do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options,
export_stats_t stats)
{
IOBUF out = NULL;
int any, rc;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
memset( &zfx, 0, sizeof zfx);
rc = open_outfile (-1, NULL, 0, !!secret, &out );
if (rc)
return rc;
if ( opt.armor && !(options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)) )
{
afx = new_armor_context ();
afx->what = secret? 5 : 1;
push_armor_filter (afx, out);
}
rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any);
if ( rc || !any )
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* Release an entire subkey list. */
static void
release_subkey_list (subkey_list_t list)
{
while (list)
{
subkey_list_t tmp = list->next;;
xfree (list);
list = tmp;
}
}
/* Returns true if NODE is a subkey and contained in LIST. */
static int
subkey_in_list_p (subkey_list_t list, KBNODE node)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
u32 kid[2];
keyid_from_pk (node->pkt->pkt.public_key, kid);
for (; list; list = list->next)
if (list->kid[0] == kid[0] && list->kid[1] == kid[1])
return 1;
}
return 0;
}
/* Allocate a new subkey list item from NODE. */
static subkey_list_t
new_subkey_list_item (KBNODE node)
{
subkey_list_t list = xcalloc (1, sizeof *list);
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
keyid_from_pk (node->pkt->pkt.public_key, list->kid);
return list;
}
/* Helper function to check whether the subkey at NODE actually
matches the description at DESC. The function returns true if the
key under question has been specified by an exact specification
(keyID or fingerprint) and does match the one at NODE. It is
assumed that the packet at NODE is either a public or secret
subkey. */
int
exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node)
{
u32 kid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
int result = 0;
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
keyid_from_pk (node->pkt->pkt.public_key, kid);
break;
case KEYDB_SEARCH_MODE_FPR:
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
break;
default:
break;
}
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
if (desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen))
result = 1;
break;
default:
break;
}
return result;
}
/* Return an error if the key represented by the S-expression S_KEY
* and the OpenPGP key represented by PK do not use the same curve. */
static gpg_error_t
match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
{
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char *curve_str = NULL;
char *flag;
const char *oidstr = NULL;
gcry_mpi_t curve_as_mpi = NULL;
gpg_error_t err;
int is_eddsa = 0;
int idx = 0;
if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH
|| pk->pubkey_algo==PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
return gpg_error (GPG_ERR_PUBKEY_ALGO);
curve = gcry_sexp_find_token (s_key, "curve", 0);
if (!curve)
{
log_error ("no reported curve\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
curve_str = gcry_sexp_nth_string (curve, 1);
gcry_sexp_release (curve); curve = NULL;
if (!curve_str)
{
log_error ("no curve name\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
oidstr = openpgp_curve_to_oid (curve_str, NULL, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve_str);
xfree (curve_str);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
xfree (curve_str);
err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
if (err)
return err;
if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi))
{
log_error ("curves do not match\n");
gcry_mpi_release (curve_as_mpi);
return gpg_error (GPG_ERR_INV_CURVE);
}
gcry_mpi_release (curve_as_mpi);
flags = gcry_sexp_find_token (s_key, "flags", 0);
if (flags)
{
for (idx = 1; idx < gcry_sexp_length (flags); idx++)
{
flag = gcry_sexp_nth_string (flags, idx);
if (flag && (strcmp ("eddsa", flag) == 0))
is_eddsa = 1;
gcry_free (flag);
}
}
if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
{
log_error ("disagreement about EdDSA\n");
err = gpg_error (GPG_ERR_INV_CURVE);
}
return err;
}
-/* Return a canonicalized public key algoithms. This is used to
+/* Return a canonicalized public key algorithms. This is used to
compare different flavors of algorithms (e.g. ELG and ELG_E are
considered the same). */
static enum gcry_pk_algos
canon_pk_algo (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S: return GCRY_PK_RSA;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E: return GCRY_PK_ELG;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH: return GCRY_PK_ECC;
default: return algo;
}
}
/* Take a cleartext dump of a secret key in PK and change the
* parameter array in PK to include the secret parameters. */
static gpg_error_t
cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t key = NULL;
char *key_type = NULL;
enum gcry_pk_algos pk_algo;
struct seckey_info *ski;
int idx, sec_start;
gcry_mpi_t pub_params[10] = { NULL };
/* we look for a private-key, then the first element in it tells us
the type */
top_list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!top_list)
goto bad_seckey;
/* ignore all S-expression after the first sublist -- we assume that
they are comments or otherwise irrelevant to OpenPGP */
if (gcry_sexp_length(top_list) < 2)
goto bad_seckey;
key = gcry_sexp_nth (top_list, 1);
if (!key)
goto bad_seckey;
key_type = gcry_sexp_nth_string(key, 0);
pk_algo = gcry_pk_map_name (key_type);
log_assert (!pk->seckey_info);
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "ne",
&pub_params[0],
&pub_params[1],
NULL);
for (idx=0; idx < 2 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
for (idx = 2; idx < 6 && !err; idx++)
{
gcry_mpi_release (pk->pkey[idx]);
pk->pkey[idx] = NULL;
}
err = gcry_sexp_extract_param (key, NULL, "dpqu",
&pk->pkey[2],
&pk->pkey[3],
&pk->pkey[4],
&pk->pkey[5],
NULL);
}
if (!err)
{
for (idx = 2; idx < 6; idx++)
ski->csum += checksum_mpi (pk->pkey[idx]);
}
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pqgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
&pub_params[3],
NULL);
for (idx=0; idx < 4 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[4]);
pk->pkey[4] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[4],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[4]);
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
NULL);
for (idx=0; idx < 3 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[3]);
pk->pkey[3] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[3],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[3]);
break;
case GCRY_PK_ECC:
err = match_curve_skey_pk (key, pk);
if (err)
goto leave;
if (!err)
err = gcry_sexp_extract_param (key, NULL, "q",
&pub_params[0],
NULL);
if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
sec_start = 2;
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
sec_start += 1;
if (!err)
{
gcry_mpi_release (pk->pkey[sec_start]);
pk->pkey[sec_start] = NULL;
err = gcry_sexp_extract_param (key, NULL, "d",
&pk->pkey[sec_start],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[sec_start]);
break;
default:
pk->seckey_info = NULL;
xfree (ski);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
leave:
gcry_sexp_release (top_list);
gcry_sexp_release (key);
gcry_free (key_type);
for (idx=0; idx < DIM(pub_params); idx++)
gcry_mpi_release (pub_params[idx]);
return err;
bad_pubkey_algo:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Use the key transfer format given in S_PGP to create the secinfo
structure in PK and change the parameter array in PK to include the
secret parameters. */
static gpg_error_t
transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
char *curve = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
enum gcry_pk_algos pk_algo;
int protect_algo = 0;
char iv[16];
int ivlen = 0;
int s2k_mode = 0;
int s2k_algo = 0;
byte s2k_salt[8];
u32 s2k_count = 0;
int is_ecdh = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
int skeyidx = 0;
struct seckey_info *ski;
/* gcry_log_debugsxp ("transferkey", s_pgp); */
top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0);
if (!top_list)
goto bad_seckey;
list = gcry_sexp_find_token (top_list, "version", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4'))
goto bad_seckey;
is_v4 = (value[0] == '4');
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "protection", 0);
if (!list)
goto bad_seckey;
value = gcry_sexp_nth_data (list, 1, &valuelen);
if (!value)
goto bad_seckey;
if (valuelen == 4 && !memcmp (value, "sha1", 4))
is_protected = 2;
else if (valuelen == 3 && !memcmp (value, "sum", 3))
is_protected = 1;
else if (valuelen == 4 && !memcmp (value, "none", 4))
is_protected = 0;
else
goto bad_seckey;
if (is_protected)
{
string = gcry_sexp_nth_string (list, 2);
if (!string)
goto bad_seckey;
protect_algo = gcry_cipher_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 3, &valuelen);
if (!value || !valuelen || valuelen > sizeof iv)
goto bad_seckey;
memcpy (iv, value, valuelen);
ivlen = valuelen;
string = gcry_sexp_nth_string (list, 4);
if (!string)
goto bad_seckey;
s2k_mode = strtol (string, NULL, 10);
xfree (string);
string = gcry_sexp_nth_string (list, 5);
if (!string)
goto bad_seckey;
s2k_algo = gcry_md_map_name (string);
xfree (string);
value = gcry_sexp_nth_data (list, 6, &valuelen);
if (!value || !valuelen || valuelen > sizeof s2k_salt)
goto bad_seckey;
memcpy (s2k_salt, value, valuelen);
string = gcry_sexp_nth_string (list, 7);
if (!string)
goto bad_seckey;
s2k_count = strtoul (string, NULL, 10);
xfree (string);
}
/* Parse the gcrypt PK algo and check that it is okay. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "algo", 0);
if (!list)
goto bad_seckey;
string = gcry_sexp_nth_string (list, 1);
if (!string)
goto bad_seckey;
pk_algo = gcry_pk_map_name (string);
xfree (string); string = NULL;
if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
|| gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
|| !npkey || npkey >= nskey)
goto bad_seckey;
/* Check that the pubkey algo matches the one from the public key. */
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ECC:
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
;
else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
is_ecdh = 1;
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
;
else
pk_algo = 0; /* Does not match. */
/* For ECC we do not have the domain parameters thus fix our info. */
npkey = 1;
nskey = 2;
break;
default:
pk_algo = 0; /* Oops. */
break;
}
if (!pk_algo)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
/* This check has to go after the ecc adjustments. */
if (nskey > PUBKEY_MAX_NSKEY)
goto bad_seckey;
/* Parse the key parameters. */
gcry_sexp_release (list);
list = gcry_sexp_find_token (top_list, "skey", 0);
if (!list)
goto bad_seckey;
for (idx=0;;)
{
int is_enc;
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value && skeyidx >= npkey)
break; /* Ready. */
/* Check for too many parameters. Note that depending on the
protection mode and version number we may see less than NSKEY
(but at least NPKEY+1) parameters. */
if (idx >= 2*nskey)
goto bad_seckey;
if (skeyidx >= DIM (skey)-1)
goto bad_seckey;
if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e'))
goto bad_seckey;
is_enc = (value[0] == 'e');
value = gcry_sexp_nth_data (list, ++idx, &valuelen);
if (!value || !valuelen)
goto bad_seckey;
if (is_enc)
{
void *p = xtrymalloc (valuelen);
if (!p)
goto outofmem;
memcpy (p, value, valuelen);
skey[skeyidx] = gcry_mpi_set_opaque (NULL, p, valuelen*8);
if (!skey[skeyidx])
goto outofmem;
}
else
{
if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD,
value, valuelen, NULL))
goto bad_seckey;
}
skeyidx++;
}
skey[skeyidx++] = NULL;
gcry_sexp_release (list); list = NULL;
/* We have no need for the CSUM value thus we don't parse it. */
/* list = gcry_sexp_find_token (top_list, "csum", 0); */
/* if (list) */
/* { */
/* string = gcry_sexp_nth_string (list, 1); */
/* if (!string) */
/* goto bad_seckey; */
/* desired_csum = strtoul (string, NULL, 10); */
/* xfree (string); */
/* } */
/* else */
/* desired_csum = 0; */
/* gcry_sexp_release (list); list = NULL; */
/* Get the curve name if any, */
list = gcry_sexp_find_token (top_list, "curve", 0);
if (list)
{
curve = gcry_sexp_nth_string (list, 1);
gcry_sexp_release (list); list = NULL;
}
gcry_sexp_release (top_list); top_list = NULL;
/* log_debug ("XXX is_v4=%d\n", is_v4); */
/* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */
/* log_debug ("XXX is_protected=%d\n", is_protected); */
/* log_debug ("XXX protect_algo=%d\n", protect_algo); */
/* log_printhex ("XXX iv", iv, ivlen); */
/* log_debug ("XXX ivlen=%d\n", ivlen); */
/* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */
/* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */
/* log_printhex ("XXX s2k_salt", s2k_salt, sizeof s2k_salt); */
/* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */
/* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */
/* if (is_enc) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
if (!is_v4 || is_protected != 2 )
{
/* We only support the v4 format and a SHA-1 checksum. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
/* We need to change the received parameters for ECC algorithms.
The transfer format has the curve name and the parameters
separate. We put them all into the SKEY array. */
if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
{
const char *oidstr;
/* Assert that all required parameters are available. We also
check that the array does not contain more parameters than
needed (this was used by some beta versions of 2.1. */
if (!curve || !skey[0] || !skey[1] || skey[2])
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = openpgp_curve_to_oid (curve, NULL, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve);
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
/* Put the curve's OID into the MPI array. This requires
that we shift Q and D. For ECDH also insert the KDF parms. */
if (is_ecdh)
{
skey[4] = NULL;
skey[3] = skey[1];
skey[2] = gcry_mpi_copy (pk->pkey[2]);
}
else
{
skey[3] = NULL;
skey[2] = skey[1];
}
skey[1] = skey[0];
skey[0] = NULL;
err = openpgp_oid_from_str (oidstr, skey + 0);
if (err)
goto leave;
/* Fixup the NPKEY and NSKEY to match OpenPGP reality. */
npkey = 2 + is_ecdh;
nskey = 3 + is_ecdh;
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* log_info ("YYY skey[%d]:", idx); */
/* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
}
/* Do some sanity checks. */
if (s2k_count > 255)
{
/* We expect an already encoded S2K count. */
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_cipher_test_algo (protect_algo);
if (err)
goto leave;
err = openpgp_md_test_algo (s2k_algo);
if (err)
goto leave;
/* Check that the public key parameters match. Note that since
Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */
for (idx=0; idx < npkey; idx++)
if (gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* Check that the first secret key parameter in SKEY is encrypted
and that there are no more secret key parameters. The latter is
guaranteed by the v4 packet format. */
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_OPAQUE))
goto bad_seckey;
if (npkey+1 < DIM (skey) && skey[npkey+1])
goto bad_seckey;
/* Check that the secret key parameters in PK are all set to NULL. */
for (idx=npkey; idx < nskey; idx++)
if (pk->pkey[idx])
goto bad_seckey;
/* Now build the protection info. */
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
ski->sha1chk = 1;
ski->algo = protect_algo;
ski->s2k.mode = s2k_mode;
ski->s2k.hash_algo = s2k_algo;
log_assert (sizeof ski->s2k.salt == sizeof s2k_salt);
memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
ski->s2k.count = s2k_count;
log_assert (ivlen <= sizeof ski->iv);
memcpy (ski->iv, iv, ivlen);
ski->ivlen = ivlen;
/* Store the protected secret key parameter. */
pk->pkey[npkey] = skey[npkey];
skey[npkey] = NULL;
/* That's it. */
leave:
gcry_free (curve);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Print an "EXPORTED" status line. PK is the primary public key. */
static void
print_status_exported (PKT_public_key *pk)
{
char *hexfpr;
if (!is_status_enabled ())
return;
hexfpr = hexfingerprint (pk, NULL, 0);
write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]");
xfree (hexfpr);
}
/*
* Receive a secret key from agent specified by HEXGRIP.
*
* Since the key data from the agent is encrypted, decrypt it using
* CIPHERHD context. Then, parse the decrypted key data into transfer
* format, and put secret parameters into PK.
*
* If CLEARTEXT is 0, store the secret key material
* passphrase-protected. Otherwise, store secret key material in the
* clear.
*
* CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals.
*/
gpg_error_t
receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext,
char **cache_nonce_addr, const char *hexgrip,
PKT_public_key *pk)
{
gpg_error_t err = 0;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
unsigned char *key = NULL;
size_t keylen, realkeylen;
gcry_sexp_t s_skey;
char *prompt;
if (opt.verbose)
log_info ("key %s: asking agent for the secret parts\n", hexgrip);
prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT,1);
err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, cache_nonce_addr,
&wrappedkey, &wrappedkeylen,
pk->keyid, pk->main_keyid, pk->pubkey_algo);
xfree (prompt);
if (err)
goto unwraperror;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto unwraperror;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto unwraperror;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto unwraperror;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto unwraperror; /* Invalid csexp. */
err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
if (!err)
{
if (cleartext)
err = cleartext_secret_key_to_openpgp (s_skey, pk);
else
err = transfer_format_to_openpgp (s_skey, pk);
gcry_sexp_release (s_skey);
}
unwraperror:
xfree (key);
xfree (wrappedkey);
if (err)
{
log_error ("key %s: error receiving key from agent:"
" %s%s\n", hexgrip, gpg_strerror (err),
gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
"":_(" - skipped"));
}
return err;
}
/* Write KEYBLOCK either to stdout or to the file set with the
* --output option. This is a simplified version of do_export_stream
* which supports only a few export options. */
gpg_error_t
write_keyblock_to_output (kbnode_t keyblock, int with_armor,
unsigned int options)
{
gpg_error_t err;
const char *fname;
iobuf_t out;
kbnode_t node;
armor_filter_context_t *afx = NULL;
iobuf_t out_help = NULL;
PKT_public_key *pk = NULL;
fname = opt.outfile? opt.outfile : "-";
if (is_secured_filename (fname) )
return gpg_error (GPG_ERR_EPERM);
out = iobuf_create (fname, 0);
if (!out)
{
err = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out));
if ((options & (EXPORT_PKA_FORMAT|EXPORT_DANE_FORMAT)))
{
with_armor = 0;
out_help = iobuf_temp ();
}
if (with_armor)
{
afx = new_armor_context ();
afx->what = 1;
push_armor_filter (afx, out);
}
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_RING_TRUST)
continue; /* Skip - they should not be here anyway. */
if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY))
pk = node->pkt->pkt.public_key;
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out_help? out_help : out, node->pkt);
else
err = build_packet (out_help? out_help : out, node->pkt);
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err) );
goto leave;
}
}
err = 0;
if (out_help && pk)
{
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
}
leave:
if (err)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_cancel (out_help);
release_armor_context (afx);
return err;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-subkey filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_subkey_filter (ctrl_t ctrl, kbnode_t keyblock,
recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
{
/*log_debug ("drop-subkey: deleting a key\n");*/
/* The subkey packet and all following packets up to the
* next subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
}
}
}
/* Print DANE or PKA records for all user IDs in KEYBLOCK to OUT. The
* data for the record is taken from (DATA,DATELEN). PK is the public
* key packet with the primary key. */
static gpg_error_t
print_pka_or_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen,
int print_pka, int print_dane)
{
gpg_error_t err = 0;
kbnode_t kbctx, node;
PKT_user_id *uid;
char *mbox = NULL;
char hashbuf[32];
char *hash = NULL;
char *domain;
const char *s;
unsigned int len;
estream_t fp = NULL;
char *hexdata = NULL;
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
if (!hexfpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
hexdata = bin2hex (data, datalen, NULL);
if (!hexdata)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hexdata);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype != PKT_USER_ID)
continue;
uid = node->pkt->pkt.user_id;
if (uid->flags.expired || uid->flags.revoked)
continue;
xfree (mbox);
mbox = mailbox_from_userid (uid->name, 0);
if (!mbox)
continue;
domain = strchr (mbox, '@');
*domain++ = 0;
if (print_pka)
{
es_fprintf (fp, "$ORIGIN _pka.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = zb32_encode (hashbuf, 8*20);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
len = strlen (hexfpr)/2;
es_fprintf (fp, "%s TYPE37 \\# %u 0006 0000 00 %02X %s\n\n",
hash, 6 + len, len, hexfpr);
}
if (print_dane && hexdata)
{
es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = bin2hex (hashbuf, 28, NULL);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hash);
len = strlen (hexdata)/2;
es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len);
for (s = hexdata; ;)
{
es_fprintf (fp, "\t%.64s\n", s);
if (strlen (s) < 64)
break;
s += 64;
}
es_fputs ("\t)\n\n", fp);
}
}
/* Make sure it is a string and write it. */
es_fputc (0, fp);
{
void *vp;
if (es_fclose_snatch (fp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = NULL;
iobuf_writestr (out, vp);
es_free (vp);
}
err = 0;
leave:
xfree (hash);
xfree (mbox);
es_fclose (fp);
xfree (hexdata);
xfree (hexfpr);
return err;
}
/* Helper for do_export_stream which writes one keyblock to OUT. */
static gpg_error_t
do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
iobuf_t out, int secret, unsigned int options,
export_stats_t stats, int *any,
KEYDB_SEARCH_DESC *desc, size_t ndesc,
size_t descindex, gcry_cipher_hd_t cipherhd)
{
gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND);
char *cache_nonce = NULL;
subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */
int skip_until_subkey = 0;
int cleartext = 0;
char *hexgrip = NULL;
char *serialno = NULL;
PKT_public_key *pk;
u32 subkidbuf[2], *subkid;
kbnode_t kbctx, node;
/* NB: walk_kbnode skips packets marked as deleted. */
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (skip_until_subkey)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
skip_until_subkey = 0;
else
continue;
}
/* We used to use comment packets, but not any longer. In
* case we still have comments on a key, strip them here
* before we call build_packet(). */
if (node->pkt->pkttype == PKT_COMMENT)
continue;
/* Skip ring trust packets - they should not be here anyway. */
if (node->pkt->pkttype == PKT_RING_TRUST)
continue;
/* If exact is set, then we only export what was requested
* (plus the primary key, if the user didn't specifically
* request it). */
if (desc[descindex].exact && node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (!exact_subkey_match_p (desc+descindex, node))
{
/* Before skipping this subkey, check whether any
* other description wants an exact match on a
* subkey and include that subkey into the output
* too. Need to add this subkey to a list so that
* it won't get processed a second time.
*
* So the first step here is to check that list and
* skip in any case if the key is in that list.
*
* We need this whole mess because the import
* function of GnuPG < 2.1 is not able to merge
* secret keys and thus it is useless to output them
* as two separate keys and have import merge them.
*/
if (subkey_in_list_p (subkey_list, node))
skip_until_subkey = 1; /* Already processed this one. */
else
{
size_t j;
for (j=0; j < ndesc; j++)
if (j != descindex && desc[j].exact
&& exact_subkey_match_p (desc+j, node))
break;
if (!(j < ndesc))
skip_until_subkey = 1; /* No other one matching. */
}
}
if (skip_until_subkey)
continue;
/* Mark this one as processed. */
{
subkey_list_t tmp = new_subkey_list_item (node);
tmp->next = subkey_list;
subkey_list = tmp;
}
}
if (node->pkt->pkttype == PKT_SIGNATURE)
{
/* Do not export packets which are marked as not
* exportable. */
if (!(options & EXPORT_LOCAL_SIGS)
&& !node->pkt->pkt.signature->flags.exportable)
continue; /* not exportable */
/* Do not export packets with a "sensitive" revocation key
* unless the user wants us to. Note that we do export
* these when issuing the actual revocation (see revoke.c). */
if (!(options & EXPORT_SENSITIVE_REVKEYS)
&& node->pkt->pkt.signature->revkey)
{
int i;
for (i = 0; i < node->pkt->pkt.signature->numrevkeys; i++)
if ((node->pkt->pkt.signature->revkey[i].class & 0x40))
break;
if (i < node->pkt->pkt.signature->numrevkeys)
continue;
}
}
/* Don't export user ids (and attributes)? This is not RFC-4880
* compliant but we allow it anyway. */
if ((options & EXPORT_DROP_UIDS)
&& node->pkt->pkttype == PKT_USER_ID)
{
/* Skip until we get to something that is not a user id (or
* attrib) or a signature on it. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
/* Don't export attribs? */
if (!(options & EXPORT_ATTRIBUTES)
&& node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data)
{
/* Skip until we get to something that is not an attrib or a
* signature on an attrib. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
if (secret && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
pk = node->pkt->pkt.public_key;
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
subkid = NULL;
else
{
keyid_from_pk (pk, subkidbuf);
subkid = subkidbuf;
}
if (pk->seckey_info)
{
log_error ("key %s: oops: seckey_info already set"
" - skipped\n", keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
continue;
}
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("key %s: error computing keygrip: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
continue;
}
xfree (serialno);
serialno = NULL;
if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* We are asked not to export the secret parts of the
* primary key. Make up an error code to create the
* stub. */
err = GPG_ERR_NOT_FOUND;
}
else
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext);
if ((!err && serialno)
&& secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* It does not make sense to export a key with its
* primary key on card using a non-key stub. Thus we
* skip those keys when used with --export-secret-subkeys. */
log_info (_("key %s: key material on-card - skipped\n"),
keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND
|| (!err && serialno))
{
/* Create a key stub. */
struct seckey_info *ski;
const char *s;
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
if (err)
ski->s2k.mode = 1001; /* GNU dummy (no secret key). */
else
{
ski->s2k.mode = 1002; /* GNU-divert-to-card. */
for (s=serialno; sizeof (ski->ivlen) && *s && s[1];
ski->ivlen++, s += 2)
ski->iv[ski->ivlen] = xtoi_2 (s);
}
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
else if (!err)
{
err = receive_seckey_from_agent (ctrl, cipherhd,
cleartext, &cache_nonce,
hexgrip, pk);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
goto leave;
skip_until_subkey = 1;
err = 0;
}
else
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
}
else
{
log_error ("key %s: error getting keyinfo from agent: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
}
xfree (pk->seckey_info);
pk->seckey_info = NULL;
{
int i;
for (i = pubkey_get_npkey (pk->pubkey_algo);
i < pubkey_get_nskey (pk->pubkey_algo); i++)
{
gcry_mpi_release (pk->pkey[i]);
pk->pkey[i] = NULL;
}
}
}
else /* Not secret or common packets. */
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err));
goto leave;
}
if (!skip_until_subkey)
*any = 1;
}
leave:
release_subkey_list (subkey_list);
xfree (serialno);
xfree (hexgrip);
xfree (cache_nonce);
return err;
}
/* Export the keys identified by the list of strings in USERS to the
stream OUT. If SECRET is false public keys will be exported. With
secret true secret keys will be exported; in this case 1 means the
entire secret keyblock and 2 only the subkeys. OPTIONS are the
export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit
code is zero, a pointer to the first keyblock found and exported
will be stored at this address; no other keyblocks are exported in
this case. The caller must free the returned keyblock. If any
key has been exported true is stored at ANY. */
static int
do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any)
{
gpg_error_t err = 0;
PACKET pkt;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t ndesc, descindex;
KEYDB_SEARCH_DESC *desc = NULL;
KEYDB_HANDLE kdbhd;
strlist_t sl;
gcry_cipher_hd_t cipherhd = NULL;
struct export_stats_s dummystats;
iobuf_t out_help = NULL;
if (!stats)
stats = &dummystats;
*any = 0;
init_packet (&pkt);
kdbhd = keydb_new (ctrl);
if (!kdbhd)
return gpg_error_from_syserror ();
/* For the PKA and DANE format open a helper iobuf and for DANE
* enforce some options. */
if ((options & (EXPORT_PKA_FORMAT | EXPORT_DANE_FORMAT)))
{
out_help = iobuf_temp ();
if ((options & EXPORT_DANE_FORMAT))
options |= EXPORT_MINIMAL | EXPORT_CLEAN;
}
if (!users)
{
ndesc = 1;
desc = xcalloc (ndesc, sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
if (!(err=classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have
a found flag for each entry in desc. To set this flag we
must check all those entries after a match to mark all
matched one - currently we stop at the first match. To do
this we need an extra flag to enable this feature. */
}
#ifdef ENABLE_SELINUX_HACKS
if (secret)
{
log_error (_("exporting secret keys not allowed\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
#endif
/* For secret key export we need to setup a decryption context. */
if (secret)
{
void *kek = NULL;
size_t keklen;
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
xfree (kek);
kek = NULL;
}
for (;;)
{
u32 keyid[2];
PKT_public_key *pk;
err = keydb_search (kdbhd, desc, ndesc, &descindex);
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
if (err)
break;
/* Read the keyblock. */
release_kbnode (keyblock);
keyblock = NULL;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("public key packet not found in keyblock - skipped\n");
continue;
}
stats->count++;
setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* If a secret key export is required we need to check whether
we have a secret key at all and if so create the seckey_info
structure. */
if (secret)
{
if (agent_probe_any_secret_key (ctrl, keyblock))
continue; /* No secret key (neither primary nor subkey). */
/* No v3 keys with GNU mode 1001. */
if (secret == 2 && pk->version == 3)
{
log_info (_("key %s: PGP 2.x style key - skipped\n"),
keystr (keyid));
continue;
}
/* The agent does not yet allow export of v3 packets. It is
actually questionable whether we should allow them at
all. */
if (pk->version == 3)
{
log_info ("key %s: PGP 2.x style key (v3) export "
"not yet supported - skipped\n", keystr (keyid));
continue;
}
stats->secret_count++;
}
/* Always do the cleaning on the public key part if requested.
* A designated revocation is never stripped, even with
* export-minimal set. */
if ((options & EXPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose,
(options&EXPORT_MINIMAL), NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose,
(options&EXPORT_MINIMAL)? KEY_CLEAN_ALL
/**/ : KEY_CLEAN_AUTHENCR,
NULL, NULL);
commit_kbnode (&keyblock);
}
if (export_keep_uid)
{
commit_kbnode (&keyblock);
apply_keep_uid_filter (ctrl, keyblock, export_keep_uid);
commit_kbnode (&keyblock);
}
if (export_drop_subkey)
{
commit_kbnode (&keyblock);
apply_drop_subkey_filter (ctrl, keyblock, export_drop_subkey);
commit_kbnode (&keyblock);
}
/* And write it. */
err = do_export_one_keyblock (ctrl, keyblock, keyid,
out_help? out_help : out,
secret, options, stats, any,
desc, ndesc, descindex, cipherhd);
if (err)
break;
if (keyblock_out)
{
*keyblock_out = keyblock;
break;
}
if (out_help)
{
/* We want to write PKA or DANE records. OUT_HELP has the
* keyblock and we print a record for each uid to OUT. */
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_pka_or_dane_records (out,
keyblock, pk, data, datalen,
(options & EXPORT_PKA_FORMAT),
(options & EXPORT_DANE_FORMAT));
if (err)
goto leave;
iobuf_close (out_help);
out_help = iobuf_temp ();
}
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
leave:
iobuf_cancel (out_help);
gcry_cipher_close (cipherhd);
xfree(desc);
keydb_release (kdbhd);
if (err || !keyblock_out)
release_kbnode( keyblock );
if( !*any )
log_info(_("WARNING: nothing exported\n"));
return err;
}
static gpg_error_t
key_to_sshblob (membuf_t *mb, const char *identifier, ...)
{
va_list arg_ptr;
gpg_error_t err = 0;
unsigned char nbuf[4];
unsigned char *buf;
size_t buflen;
gcry_mpi_t a;
ulongtobuf (nbuf, (ulong)strlen (identifier));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier);
if (!strncmp (identifier, "ecdsa-sha2-", 11))
{
ulongtobuf (nbuf, (ulong)strlen (identifier+11));
put_membuf (mb, nbuf, 4);
put_membuf_str (mb, identifier+11);
}
va_start (arg_ptr, identifier);
while ((a = va_arg (arg_ptr, gcry_mpi_t)))
{
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
if (err)
break;
if (!strcmp (identifier, "ssh-ed25519")
&& buflen > 5 && buf[4] == 0x40)
{
/* We need to strip our 0x40 prefix. */
put_membuf (mb, "\x00\x00\x00\x20", 4);
put_membuf (mb, buf+5, buflen-5);
}
else
put_membuf (mb, buf, buflen);
gcry_free (buf);
}
va_end (arg_ptr);
return err;
}
static gpg_error_t
export_one_ssh_key (estream_t fp, PKT_public_key *pk)
{
gpg_error_t err;
const char *identifier = NULL;
membuf_t mb;
struct b64state b64_state;
void *blob;
size_t bloblen;
init_membuf (&mb, 4096);
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_DSA:
identifier = "ssh-dss";
err = key_to_sshblob (&mb, identifier,
pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
NULL);
break;
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_S:
identifier = "ssh-rsa";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
break;
case PUBKEY_ALGO_ECDSA:
{
char *curveoid;
const char *curve;
curveoid = openpgp_oid_to_str (pk->pkey[0]);
if (!curveoid)
err = gpg_error_from_syserror ();
else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
if (!strcmp (curve, "nistp256"))
identifier = "ecdsa-sha2-nistp256";
else if (!strcmp (curve, "nistp384"))
identifier = "ecdsa-sha2-nistp384";
else if (!strcmp (curve, "nistp521"))
identifier = "ecdsa-sha2-nistp521";
if (!identifier)
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
xfree (curveoid);
}
break;
case PUBKEY_ALGO_EDDSA:
if (!openpgp_oid_is_ed25519 (pk->pkey[0]))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
identifier = "ssh-ed25519";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
break;
default:
err = GPG_ERR_PUBKEY_ALGO;
break;
}
if (err)
goto leave;
err = b64enc_start_es (&b64_state, fp, "");
if (err)
goto leave;
blob = get_membuf (&mb, &bloblen);
if (blob)
{
es_fprintf (fp, "%s ", identifier);
err = b64enc_write (&b64_state, blob, bloblen);
es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
xfree (blob);
}
b64enc_finish (&b64_state);
leave:
xfree (get_membuf (&mb, NULL));
return err;
}
/* Export the key identified by USERID in the SSH public key format.
The function exports the latest subkey with Authentication
capability unless the '!' suffix is used to export a specific
key. */
gpg_error_t
export_ssh_key (ctrl_t ctrl, const char *userid)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
u32 latest_date;
u32 curtime = make_timestamp ();
kbnode_t latest_key, node;
PKT_public_key *pk;
estream_t fp = NULL;
const char *fname = "-";
/* We need to know whether the key has been specified using the
exact syntax ('!' suffix). Thus we need to run a
classify_user_id on our own. */
err = classify_user_id (userid, &desc, 1);
/* Get the public key. */
if (!err)
{
getkey_ctx_t getkeyctx;
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
&getkeyctx, NULL, userid, &keyblock,
NULL,
0 /* Only usable keys or given exact. */);
if (!err)
{
err = getkey_next (ctrl, getkeyctx, NULL, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
err = 0;
}
getkey_end (ctrl, getkeyctx);
}
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
return err;
}
/* The finish_lookup code in getkey.c does not handle auth keys,
thus we have to duplicate the code here to find the latest
subkey. However, if the key has been found using an exact match
('!' notation) we use that key without any further checks and
even allow the use of the primary key. */
latest_date = 0;
latest_key = NULL;
for (node = keyblock; node; node = node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_PUBLIC_KEY)
&& node->pkt->pkt.public_key->flags.exact)
{
latest_key = node;
break;
}
}
if (!latest_key)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not usable for authentication\n");
continue;
}
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure that it
is used. A better change would be to compare ">=" but that
might also change the selected keys and is as such a more
intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = node;
}
}
/* If no subkey was suitable check the primary key. */
if (!latest_key
&& (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking primary key %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not usable for authentication\n");
}
else if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not yet valid\n");
}
else
{
if (DBG_LOOKUP)
log_debug ("\tprimary key is fine\n");
latest_date = pk->timestamp;
latest_key = node;
}
}
}
if (!latest_key)
{
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
goto leave;
}
pk = latest_key->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
fp = es_fopen ((fname = opt.outfile), "w");
else
fp = es_stdout;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
err = export_one_ssh_key (fp, pk);
if (err)
goto leave;
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
{
if (es_fclose (fp))
err = gpg_error_from_syserror ();
fp = NULL;
}
if (err)
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
leave:
es_fclose (fp);
release_kbnode (keyblock);
return err;
}
diff --git a/g10/getkey.c b/g10/getkey.c
index abd44d983..bc49782d3 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -1,4252 +1,4252 @@
/* getkey.c - Get a key from the database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "options.h"
#include "main.h"
#include "trustdb.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "objcache.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
#include "../common/status.h"
#define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE
#define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE
#if MAX_PK_CACHE_ENTRIES < 2
#error We need the cache for key creation
#endif
/* Flags values returned by the lookup code. Note that the values are
* directly used by the KEY_CONSIDERED status line. */
#define LOOKUP_NOT_SELECTED (1<<0)
#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */
/* A context object used by the lookup functions. */
struct getkey_ctx_s
{
/* Part of the search criteria: whether the search is an exact
search or not. A search that is exact requires that a key or
subkey meet all of the specified criteria. A search that is not
exact allows selecting a different key or subkey from the
keyblock that matched the criteria. Further, an exact search
returns the key or subkey that matched whereas a non-exact search
typically returns the primary key. See finish_lookup for
details. */
int exact;
/* Part of the search criteria: Whether the caller only wants keys
with an available secret key. This is used by getkey_next to get
the next result with the same initial criteria. */
int want_secret;
/* Part of the search criteria: The type of the requested key. A
mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT.
If non-zero, then for a key to match, it must implement one of
the required uses. */
int req_usage;
/* The database handle. */
KEYDB_HANDLE kr_handle;
/* Whether we should call xfree() on the context when the context is
released using getkey_end()). */
int not_allocated;
/* This variable is used as backing store for strings which have
their address used in ITEMS. */
strlist_t extra_list;
/* Hack to return the mechanism (AKL_foo) used to find the key. */
int found_via_akl;
/* Part of the search criteria: The low-level search specification
as passed to keydb_search. */
int nitems;
/* This must be the last element in the structure. When we allocate
the structure, we allocate it so that ITEMS can hold NITEMS. */
KEYDB_SEARCH_DESC items[1];
};
#if 0
static struct
{
int any;
int okay_count;
int nokey_count;
int error_count;
} lkup_stats[21];
#endif
typedef struct keyid_list
{
struct keyid_list *next;
byte fprlen;
char fpr[MAX_FINGERPRINT_LEN];
u32 keyid[2];
} *keyid_list_t;
#if MAX_PK_CACHE_ENTRIES
typedef struct pk_cache_entry
{
struct pk_cache_entry *next;
u32 keyid[2];
PKT_public_key *pk;
} *pk_cache_entry_t;
static pk_cache_entry_t pk_cache;
static int pk_cache_entries; /* Number of entries in pk cache. */
static int pk_cache_disabled;
#endif
#if MAX_UID_CACHE_ENTRIES < 5
#error we really need the userid cache
#endif
static void merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock);
static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key);
static kbnode_t finish_lookup (kbnode_t keyblock,
unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags);
static void print_status_key_considered (kbnode_t keyblock, unsigned int flags);
#if 0
static void
print_stats ()
{
int i;
for (i = 0; i < DIM (lkup_stats); i++)
{
if (lkup_stats[i].any)
es_fprintf (es_stderr,
"lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n",
i,
lkup_stats[i].okay_count,
lkup_stats[i].nokey_count, lkup_stats[i].error_count);
}
}
#endif
/* Cache a copy of a public key in the public key cache. PK is not
* cached if caching is disabled (via getkey_disable_caches), if
* PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id
* from the public key (e.g., unsupported algorithm), or a key with
* the key id is already in the cache.
*
* The public key packet is copied into the cache using
* copy_public_key. Thus, any secret parts are not copied, for
* instance.
*
* This cache is filled by get_pubkey and is read by get_pubkey and
* get_pubkey_fast. */
void
cache_public_key (PKT_public_key * pk)
{
#if MAX_PK_CACHE_ENTRIES
pk_cache_entry_t ce, ce2;
u32 keyid[2];
if (pk_cache_disabled)
return;
if (pk->flags.dont_cache)
return;
if (is_ELGAMAL (pk->pubkey_algo)
|| pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH
|| is_RSA (pk->pubkey_algo))
{
keyid_from_pk (pk, keyid);
}
else
return; /* Don't know how to get the keyid. */
for (ce = pk_cache; ce; ce = ce->next)
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
{
if (DBG_CACHE)
log_debug ("cache_public_key: already in cache\n");
return;
}
if (pk_cache_entries >= MAX_PK_CACHE_ENTRIES)
{
int n;
/* Remove the last 50% of the entries. */
for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++)
ce = ce->next;
if (ce && ce != pk_cache && ce->next)
{
ce2 = ce->next;
ce->next = NULL;
ce = ce2;
for (; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
pk_cache_entries--;
}
}
log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES);
}
pk_cache_entries++;
ce = xmalloc (sizeof *ce);
ce->next = pk_cache;
pk_cache = ce;
ce->pk = copy_public_key (NULL, pk);
ce->keyid[0] = keyid[0];
ce->keyid[1] = keyid[1];
#endif
}
/* Return a const utf-8 string with the text "[User ID not found]".
This function is required so that we don't need to switch gettext's
encoding temporary. */
static const char *
user_id_not_found_utf8 (void)
{
static char *text;
if (!text)
text = native_to_utf8 (_("[User ID not found]"));
return text;
}
/* Disable and drop the public key cache (which is filled by
cache_public_key and get_pubkey). Note: there is currently no way
to re-enable this cache. */
void
getkey_disable_caches ()
{
#if MAX_PK_CACHE_ENTRIES
{
pk_cache_entry_t ce, ce2;
for (ce = pk_cache; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
}
pk_cache_disabled = 1;
pk_cache_entries = 0;
pk_cache = NULL;
}
#endif
/* fixme: disable user id cache ? */
}
/* Free a list of pubkey_t objects. */
void
pubkeys_free (pubkey_t keys)
{
while (keys)
{
pubkey_t next = keys->next;
xfree (keys->pk);
release_kbnode (keys->keyblock);
xfree (keys);
keys = next;
}
}
static void
pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key)
{
kbnode_t a = found_key ? found_key : keyblock;
log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY
|| a->pkt->pkttype == PKT_PUBLIC_SUBKEY);
copy_public_key (pk, a->pkt->pkt.public_key);
}
/* Specialized version of get_pubkey which retrieves the key based on
* information in SIG. In contrast to get_pubkey PK is required. */
gpg_error_t
get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig)
{
const byte *fpr;
size_t fprlen;
/* First try the new ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen))
return 0;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkey (ctrl, pk, sig->keyid);
}
/* Return the public key with the key id KEYID and store it at PK.
* The resources in *PK should be released using
* release_public_key_parts(). This function also stores a copy of
* the public key in the user id cache (see cache_public_key).
*
* If PK is NULL, this function just stores the public key in the
* cache and returns the usual return code.
*
* PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the
* lookup function. If this is non-zero, only keys with the specified
* usage will be returned. As such, it is essential that
* PK->REQ_USAGE be correctly initialized!
*
* Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key
* with the specified key id, or another error code if an error
* occurs.
*
* If the data was not read from the cache, then the self-signed data
* has definitely been merged into the public key using
* merge_selfsigs. */
int
get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int internal = 0;
int rc = 0;
#if MAX_PK_CACHE_ENTRIES
if (pk)
{
/* Try to get it from the cache. We don't do this when pk is
NULL as it does not guarantee that the user IDs are
cached. */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
/* XXX: We don't check PK->REQ_USAGE here, but if we don't
read from the cache, we do check it! */
{
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
/* More init stuff. */
if (!pk)
{
internal++;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
/* Do a lookup. */
{
struct getkey_ctx_s ctx;
kbnode_t kb = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
if (ctrl && ctrl->cached_getkey_kdb)
{
ctx.kr_handle = ctrl->cached_getkey_kdb;
ctrl->cached_getkey_kdb = NULL;
keydb_search_reset (ctx.kr_handle);
}
else
{
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc)
{
pk_from_block (pk, kb, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (kb);
}
if (!rc)
goto leave;
rc = GPG_ERR_NO_PUBKEY;
leave:
if (!rc)
cache_public_key (pk);
if (internal)
free_public_key (pk);
return rc;
}
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
* account nor does it merge in the self-signed data. This function
* also only considers primary keys. It is intended to be used as a
* quick check of the key to avoid recursion. It should only be used
* in very certain cases. Like get_pubkey and unlike any of the other
* lookup functions, this function also consults the user id cache
* (see cache_public_key).
*
* Return the public key in *PK. The resources in *PK should be
* released using release_public_key_parts(). */
int
get_pubkey_fast (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int rc = 0;
KEYDB_HANDLE hd;
KBNODE keyblock;
u32 pkid[2];
log_assert (pk);
#if MAX_PK_CACHE_ENTRIES
{
/* Try to get it from the cache */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]
/* Only consider primary keys. */
&& ce->pk->keyid[0] == ce->pk->main_keyid[0]
&& ce->pk->keyid[1] == ce->pk->main_keyid[1])
{
if (pk)
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_kid (hd, keyid);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keydb_release (hd);
return GPG_ERR_NO_PUBKEY;
}
rc = keydb_get_keyblock (hd, &keyblock);
keydb_release (hd);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
return GPG_ERR_NO_PUBKEY;
}
log_assert (keyblock && keyblock->pkt
&& keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* We return the primary key. If KEYID matched a subkey, then we
return an error. */
keyid_from_pk (keyblock->pkt->pkt.public_key, pkid);
if (keyid[0] == pkid[0] && keyid[1] == pkid[1])
copy_public_key (pk, keyblock->pkt->pkt.public_key);
else
rc = GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
/* Not caching key here since it won't have all of the fields
properly set. */
return rc;
}
/* Return the entire keyblock used to create SIG. This is a
* specialized version of get_pubkeyblock.
*
* FIXME: This is a hack because get_pubkey_for_sig was already called
* and it could have used a cache to hold the key. */
kbnode_t
get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig)
{
const byte *fpr;
size_t fprlen;
kbnode_t keyblock;
/* First try the new ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, NULL, &keyblock, fpr, fprlen))
return keyblock;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkeyblock (ctrl, sig->keyid);
}
/* Return the key block for the key with key id KEYID or NULL, if an
* error occurs. Use release_kbnode() to release the key block.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
kbnode_t
get_pubkeyblock (ctrl_t ctrl, u32 * keyid)
{
struct getkey_ctx_s ctx;
int rc = 0;
KBNODE keyblock = NULL;
memset (&ctx, 0, sizeof ctx);
/* No need to set exact here because we want the entire block. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return NULL;
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
rc = lookup (ctrl, &ctx, 0, &keyblock, NULL);
getkey_end (ctrl, &ctx);
return rc ? NULL : keyblock;
}
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. The resources should be released
* using release_public_key_parts().
*
* Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE
* is passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it
* must be valid! If this is non-zero, only keys with the specified
* usage will be returned.
*
* Returns 0 on success. If a public key with the specified key id is
* not found or a secret key is not available for that public key, an
* error code is returned. Note: this function ignores legacy keys.
* An error code is also return if an error occurs.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid)
{
gpg_error_t err;
struct getkey_ctx_s ctx;
kbnode_t keyblock = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
err = lookup (ctrl, &ctx, 1, &keyblock, &found_key);
if (!err)
{
pk_from_block (pk, keyblock, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (keyblock);
if (!err)
{
if (!agent_probe_secret_key (/*ctrl*/NULL, pk))
{
release_public_key_parts (pk);
err = gpg_error (GPG_ERR_NO_SECKEY);
}
}
return err;
}
/* Skip unusable keys. A key is unusable if it is revoked, expired or
disabled or if the selected user id is revoked or expired. */
static int
skip_unusable (void *opaque, u32 * keyid, int uid_no)
{
ctrl_t ctrl = opaque;
int unusable = 0;
KBNODE keyblock;
PKT_public_key *pk;
keyblock = get_pubkeyblock (ctrl, keyid);
if (!keyblock)
{
log_error ("error checking usability status of %s\n", keystr (keyid));
goto leave;
}
pk = keyblock->pkt->pkt.public_key;
/* Is the key revoked or expired? */
if (pk->flags.revoked || pk->has_expired)
unusable = 1;
/* Is the user ID in question revoked or expired? */
if (!unusable && uid_no)
{
KBNODE node;
int uids_seen = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *user_id = node->pkt->pkt.user_id;
uids_seen ++;
if (uids_seen != uid_no)
continue;
if (user_id->flags.revoked || user_id->flags.expired)
unusable = 1;
break;
}
}
/* If UID_NO is non-zero, then the keyblock better have at least
that many UIDs. */
log_assert (uids_seen == uid_no);
}
if (!unusable)
unusable = pk_is_disabled (pk);
leave:
release_kbnode (keyblock);
return unusable;
}
/* Search for keys matching some criteria.
If RETCTX is not NULL, then the constructed context is returned in
*RETCTX so that getpubkey_next can be used to get subsequent
results. In this case, getkey_end() must be used to free the
search context. If RETCTX is not NULL, then RET_KDBHD must be
NULL.
If NAMELIST is not NULL, then a search query is constructed using
classify_user_id on each of the strings in the list. (Recall: the
database does an OR of the terms, not an AND.) If NAMELIST is
NULL, then all results are returned.
If PK is not NULL, the public key of the first result is returned
in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
set, it is used to filter the search results. See the
documentation for finish_lookup to understand exactly how this is
used. Note: The self-signed data has already been merged into the
public key using merge_selfsigs. Free *PK by calling
release_public_key_parts (or, if PK was allocated using xfree, you
can use free_public_key, which calls release_public_key_parts(PK)
and then xfree(PK)).
If WANT_SECRET is set, then only keys with an available secret key
(either locally or via key registered on a smartcard) are returned.
If INCLUDE_UNUSABLE is set, then unusable keys (see the
documentation for skip_unusable for an exact definition) are
skipped unless they are looked up by key id or by fingerprint.
If RET_KB is not NULL, the keyblock is returned in *RET_KB. This
should be freed using release_kbnode().
If RET_KDBHD is not NULL, then the new database handle used to
conduct the search is returned in *RET_KDBHD. This can be used to
get subsequent results using keydb_search_next. Note: in this
case, no advanced filtering is done for subsequent results (e.g.,
WANT_SECRET and PK->REQ_USAGE are not respected).
This function returns 0 on success. Otherwise, an error code is
returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
(if want_secret is set) is returned if the key is not found. */
static int
key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist,
PKT_public_key *pk,
int want_secret, int include_unusable,
KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd)
{
int rc = 0;
int n;
strlist_t r;
strlist_t namelist_expanded = NULL;
GETKEY_CTX ctx;
KBNODE help_kb = NULL;
KBNODE found_key = NULL;
if (retctx)
{
/* Reset the returned context in case of error. */
log_assert (!ret_kdbhd); /* Not allowed because the handle is stored
in the context. */
*retctx = NULL;
}
if (ret_kdbhd)
*ret_kdbhd = NULL;
if (!namelist)
/* No search terms: iterate over the whole DB. */
{
ctx = xmalloc_clear (sizeof *ctx);
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_FIRST;
if (!include_unusable)
{
ctx->items[0].skipfnc = skip_unusable;
ctx->items[0].skipfncvalue = ctrl;
}
}
else
{
namelist_expanded = expand_group (namelist, 1);
namelist = namelist_expanded;
/* Build the search context. */
for (n = 0, r = namelist; r; r = r->next)
n++;
/* CTX has space for a single search term at the end. Thus, we
need to allocate sizeof *CTX plus (n - 1) sizeof
CTX->ITEMS. */
ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items);
ctx->nitems = n;
for (n = 0, r = namelist; r; r = r->next, n++)
{
gpg_error_t err;
err = classify_user_id (r->d, &ctx->items[n], 1);
if (ctx->items[n].exact)
ctx->exact = 1;
if (err)
{
xfree (ctx);
rc = gpg_err_code (err); /* FIXME: remove gpg_err_code. */
goto leave;
}
if (!include_unusable
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR)
{
ctx->items[n].skipfnc = skip_unusable;
ctx->items[n].skipfncvalue = ctrl;
}
}
}
ctx->want_secret = want_secret;
ctx->kr_handle = keydb_new (ctrl);
if (!ctx->kr_handle)
{
rc = gpg_error_from_syserror ();
getkey_end (ctrl, ctx);
goto leave;
}
if (!ret_kb)
ret_kb = &help_kb;
if (pk)
{
ctx->req_usage = pk->req_usage;
}
rc = lookup (ctrl, ctx, want_secret, ret_kb, &found_key);
if (!rc && pk)
{
pk_from_block (pk, *ret_kb, found_key);
}
release_kbnode (help_kb);
if (retctx) /* Caller wants the context. */
{
if (ctx->extra_list)
{
for (r=ctx->extra_list; r->next; r = r->next)
;
r->next = namelist_expanded;
}
else
ctx->extra_list = namelist_expanded;
namelist_expanded = NULL;
*retctx = ctx;
}
else
{
if (ret_kdbhd)
{
*ret_kdbhd = ctx->kr_handle;
ctx->kr_handle = NULL;
}
getkey_end (ctrl, ctx);
}
leave:
free_strlist (namelist_expanded);
return rc;
}
/* Find a public key identified by NAME.
*
* If name appears to be a valid RFC822 mailbox (i.e., email address)
* and auto key lookup is enabled (mode != GET_PUBKEY_NO_AKL), then
* the specified auto key lookup methods (--auto-key-lookup) are used
* to import the key into the local keyring. Otherwise, just the
* local keyring is consulted.
*
* MODE can be one of:
* GET_PUBKEY_NORMAL - The standard mode
* GET_PUBKEY_NO_AKL - The auto key locate functionality is
* disabled and only the local key ring is
* considered. Note: the local key ring is
* consulted even if local is not in the
* auto-key-locate option list!
- * GET_PUBKEY_NO_LOCAL - Only the auto key locate functionaly is
+ * GET_PUBKEY_NO_LOCAL - Only the auto key locate functionality is
* used and no local search is done.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is
* passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this
* is non-zero, only keys with the specified usage will be returned.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* NAME is a string, which is turned into a search query using
* classify_user_id.
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* If RET_KDBHD is not NULL, then the new database handle used to
* conduct the search is returned in *RET_KDBHD. This can be used to
* get subsequent results using keydb_search_next or to modify the
* returned record. Note: in this case, no advanced filtering is done
* for subsequent results (e.g., PK->REQ_USAGE is not respected).
* Unlike RETCTX, this is always returned.
*
* If INCLUDE_UNUSABLE is set, then unusable keys (see the
* documentation for skip_unusable for an exact definition) are
* skipped unless they are looked up by key id or by fingerprint.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
int
get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX * retctx, PKT_public_key * pk,
const char *name, KBNODE * ret_keyblock,
KEYDB_HANDLE * ret_kdbhd, int include_unusable)
{
int rc;
strlist_t namelist = NULL;
struct akl *akl;
int is_mbox;
int nodefault = 0;
int anylocalfirst = 0;
int mechanism_type = AKL_NODEFAULT;
/* If RETCTX is not NULL, then RET_KDBHD must be NULL. */
log_assert (retctx == NULL || ret_kdbhd == NULL);
if (retctx)
*retctx = NULL;
/* Does NAME appear to be a mailbox (mail address)? */
is_mbox = is_valid_mailbox (name);
if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>'
&& name[1] != '>'
&& is_valid_mailbox_mem (name+1, strlen (name)-2))
{
/* The mailbox is in the form "<foo@example.org>" which is not
* detected by is_valid_mailbox. Set the flag but keep name as
* it is because the bracketed name is actual the better
* specification for a local search and the other methods
* extract the mail address anyway. */
is_mbox = 1;
}
/* The auto-key-locate feature works as follows: there are a number
* of methods to look up keys. By default, the local keyring is
* tried first. Then, each method listed in the --auto-key-locate is
* tried in the order it appears.
*
* This can be changed as follows:
*
* - if nodefault appears anywhere in the list of options, then
* the local keyring is not tried first, or,
*
* - if local appears anywhere in the list of options, then the
* local keyring is not tried first, but in the order in which
* it was listed in the --auto-key-locate option.
*
* Note: we only save the search context in RETCTX if the local
* method is the first method tried (either explicitly or
* implicitly). */
if (mode == GET_PUBKEY_NO_LOCAL)
nodefault = 1; /* Auto-key-locate but ignore "local". */
else if (mode != GET_PUBKEY_NO_AKL)
{
/* auto-key-locate is enabled. */
/* nodefault is true if "nodefault" or "local" appear. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL)
{
nodefault = 1;
break;
}
/* anylocalfirst is true if "local" appears before any other
search methods (except "nodefault"). */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT)
{
if (akl->type == AKL_LOCAL)
anylocalfirst = 1;
break;
}
}
if (!nodefault)
{
/* "nodefault" didn't occur. Thus, "local" is implicitly the
* first method to try. */
anylocalfirst = 1;
}
if (mode == GET_PUBKEY_NO_LOCAL)
{
/* Force using the AKL. If IS_MBOX is not set this is the final
* error code. */
rc = GPG_ERR_NO_PUBKEY;
}
else if (nodefault && is_mbox)
{
/* Either "nodefault" or "local" (explicitly) appeared in the
* auto key locate list and NAME appears to be an email address.
* Don't try the local keyring. */
rc = GPG_ERR_NO_PUBKEY;
}
else
{
/* Either "nodefault" and "local" don't appear in the auto key
* locate list (in which case we try the local keyring first) or
* NAME does not appear to be an email address (in which case we
* only try the local keyring). In this case, lookup NAME in
* the local keyring. */
add_to_strlist (&namelist, name);
rc = key_byname (ctrl, retctx, namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
/* If the requested name resembles a valid mailbox and automatic
retrieval has been enabled, we try to import the key. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& mode != GET_PUBKEY_NO_AKL
&& is_mbox)
{
/* NAME wasn't present in the local keyring (or we didn't try
* the local keyring). Since the auto key locate feature is
* enabled and NAME appears to be an email address, try the auto
* locate feature. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
{
unsigned char *fpr = NULL;
size_t fpr_len;
int did_akl_local = 0;
int no_fingerprint = 0;
const char *mechanism_string = "?";
mechanism_type = akl->type;
switch (mechanism_type)
{
case AKL_NODEFAULT:
/* This is a dummy mechanism. */
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
break;
case AKL_LOCAL:
if (mode == GET_PUBKEY_NO_LOCAL)
{
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
}
else
{
mechanism_string = "Local";
did_akl_local = 1;
if (retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
add_to_strlist (&namelist, name);
rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
break;
case AKL_CERT:
mechanism_string = "DNS CERT";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_PKA:
mechanism_string = "PKA";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_pka (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_DANE:
mechanism_string = "DANE";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_WKD:
mechanism_string = "WKD";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_LDAP:
mechanism_string = "LDAP";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_KEYSERVER:
/* Strictly speaking, we don't need to only use a valid
* mailbox for the getname search, but it helps cut down
* on the problem of searching for something like "john"
* and getting a whole lot of keys back. */
if (keyserver_any_configured (ctrl))
{
mechanism_string = "keyserver";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl, name, &fpr, &fpr_len,
opt.keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
else
{
mechanism_string = "Unconfigured keyserver";
rc = GPG_ERR_NO_PUBKEY;
}
break;
case AKL_SPEC:
{
struct keyserver_spec *keyserver;
mechanism_string = akl->spec->uri;
keyserver = keyserver_match (akl->spec);
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_name (ctrl,
name, &fpr, &fpr_len, keyserver);
glo_ctrl.in_auto_key_retrieve--;
}
break;
}
/* Use the fingerprint of the key that we actually fetched.
* This helps prevent problems where the key that we fetched
* doesn't have the same name that we used to fetch it. In
* the case of CERT and PKA, this is an actual security
* requirement as the URL might point to a key put in by an
* attacker. By forcing the use of the fingerprint, we
* won't use the attacker's key here. */
if (!rc && fpr)
{
char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (fpr_len <= MAX_FINGERPRINT_LEN);
free_strlist (namelist);
namelist = NULL;
bin2hex (fpr, fpr_len, fpr_string);
if (opt.verbose)
log_info ("auto-key-locate found fingerprint %s\n",
fpr_string);
add_to_strlist (&namelist, fpr_string);
}
else if (!rc && !fpr && !did_akl_local)
{ /* The acquisition method said no failure occurred, but
* it didn't return a fingerprint. That's a failure. */
no_fingerprint = 1;
rc = GPG_ERR_NO_PUBKEY;
}
xfree (fpr);
fpr = NULL;
if (!rc && !did_akl_local)
{ /* There was no error and we didn't do a local lookup.
* This means that we imported a key into the local
* keyring. Try to read the imported key from the
* keyring. */
if (retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
if (!rc)
{
/* Key found. */
if (opt.verbose)
log_info (_("automatically retrieved '%s' via %s\n"),
name, mechanism_string);
break;
}
if ((gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
|| opt.verbose || no_fingerprint) && *mechanism_string)
log_info (_("error retrieving '%s' via %s: %s\n"),
name, mechanism_string,
no_fingerprint ? _("No fingerprint") : gpg_strerror (rc));
}
}
if (rc && retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
if (retctx && *retctx)
{
GETKEY_CTX ctx = *retctx;
strlist_t sl;
if (ctx->extra_list)
{
for (sl=ctx->extra_list; sl->next; sl = sl->next)
;
sl->next = namelist;
}
else
ctx->extra_list = namelist;
(*retctx)->found_via_akl = mechanism_type;
}
else
free_strlist (namelist);
return rc;
}
/* Comparison machinery for get_best_pubkey_byname. */
/* First we have a struct to cache computed information about the key
* in question. */
struct pubkey_cmp_cookie
{
int valid; /* Is this cookie valid? */
PKT_public_key key; /* The key. */
PKT_user_id *uid; /* The matching UID packet. */
unsigned int validity; /* Computed validity of (KEY, UID). */
u32 creation_time; /* Creation time of the newest subkey
capable of encryption. */
};
/* Then we have a series of helper functions. */
static int
key_is_ok (const PKT_public_key *key)
{
return (! key->has_expired && ! key->flags.revoked
&& key->flags.valid && ! key->flags.disabled);
}
static int
uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid)
{
return key_is_ok (key) && ! uid->flags.revoked;
}
static int
subkey_is_ok (const PKT_public_key *sub)
{
return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
}
/* Return true if KEYBLOCK has only expired encryption subkyes. Note
* that the function returns false if the key has no encryption
* subkeys at all or the subkeys are revoked. */
static int
only_expired_enc_subkeys (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *sub;
int any = 0;
for (node = find_next_kbnode (keyblock, PKT_PUBLIC_SUBKEY);
node; node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))
{
sub = node->pkt->pkt.public_key;
if (!(sub->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
if (!subkey_is_ok (sub))
continue;
any = 1;
if (!sub->has_expired)
return 0;
}
return any? 1 : 0;
}
/* Finally this function compares a NEW key to the former candidate
* OLD. Returns < 0 if the old key is worse, > 0 if the old key is
* better, == 0 if it is a tie. */
static int
pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
struct pubkey_cmp_cookie *new, KBNODE new_keyblock)
{
kbnode_t n;
if ((new->key.pubkey_usage & PUBKEY_USAGE_ENC) == 0)
new->creation_time = 0;
else
new->creation_time = new->key.timestamp;
for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY);
n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))
{
PKT_public_key *sub = n->pkt->pkt.public_key;
if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0)
continue;
if (! subkey_is_ok (sub))
continue;
if (sub->timestamp > new->creation_time)
new->creation_time = sub->timestamp;
}
/* When new key has no encryption key, use OLD key. */
if (new->creation_time == 0)
return 1;
for (n = find_next_kbnode (new_keyblock, PKT_USER_ID);
n; n = find_next_kbnode (n, PKT_USER_ID))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
char *mbox = mailbox_from_userid (uid->name, 0);
int match = mbox ? strcasecmp (name, mbox) == 0 : 0;
xfree (mbox);
if (! match)
continue;
new->uid = scopy_user_id (uid);
new->validity =
get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK;
new->valid = 1;
if (! old->valid)
return -1; /* No OLD key. */
if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid))
return -1; /* Validity of the NEW key is better. */
if (old->validity < new->validity)
return -1; /* Validity of the NEW key is better. */
if (old->validity == new->validity && uid_is_ok (&new->key, uid)
&& old->creation_time < new->creation_time)
return -1; /* Both keys are of the same validity, but the
NEW key is newer. */
}
/* Stick with the OLD key. */
return 1;
}
/* This function works like get_pubkey_byname, but if the name
* resembles a mail address, the results are ranked and only the best
* result is returned. */
gpg_error_t
get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable)
{
gpg_error_t err;
struct getkey_ctx_s *ctx = NULL;
int is_mbox;
int wkd_tried = 0;
PKT_public_key pk0;
log_assert (ret_keyblock != NULL);
if (retctx)
*retctx = NULL;
memset (&pk0, 0, sizeof pk0);
pk0.req_usage = pk? pk->req_usage : 0;
is_mbox = is_valid_mailbox (name);
if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>'
&& name[1] != '>'
&& is_valid_mailbox_mem (name+1, strlen (name)-2))
{
/* The mailbox is in the form "<foo@example.org>" which is not
* detected by is_valid_mailbox. Set the flag but keep name as
* it is because get_pubkey_byname does an is_valid_mailbox_mem
* itself. */
is_mbox = 1;
}
start_over:
if (ctx) /* Clear in case of a start over. */
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
getkey_end (ctrl, ctx);
ctx = NULL;
}
err = get_pubkey_byname (ctrl, mode,
&ctx, &pk0, name, ret_keyblock,
NULL, include_unusable);
if (err)
{
goto leave;
}
/* If the keyblock was retrieved from the local database and the key
* has expired, do further checks. However, we can do this only if
* the caller requested a keyblock. */
if (is_mbox && ctx && ctx->found_via_akl == AKL_LOCAL)
{
u32 now = make_timestamp ();
int found;
/* If the key has expired and its origin was the WKD then try to
* get a fresh key from the WKD. We also try this if the key
* has any only expired encryption subkeys. In case we checked
* for a fresh copy in the last 3 hours we won't do that again.
* Unfortunately that does not yet work because KEYUPDATE is
* only updated during import iff the key has actually changed
* (see import.c:import_one). */
if (!wkd_tried && pk0.keyorg == KEYORG_WKD
&& (pk0.keyupdate + 3*3600) < now
&& (pk0.has_expired || only_expired_enc_subkeys (*ret_keyblock)))
{
if (opt.verbose)
log_info (_("checking for a fresh copy of an expired key via %s\n"),
"WKD");
wkd_tried = 1;
glo_ctrl.in_auto_key_retrieve++;
found = !keyserver_import_wkd (ctrl, name, 0, NULL, NULL);
glo_ctrl.in_auto_key_retrieve--;
if (found)
{
release_public_key_parts (&pk0);
goto start_over;
}
}
}
if (is_mbox && ctx)
{
/* Rank results and return only the most relevant key for encryption. */
struct pubkey_cmp_cookie best = { 0 };
struct pubkey_cmp_cookie new = { 0 };
kbnode_t new_keyblock;
copy_public_key (&new.key, &pk0);
if (pubkey_cmp (ctrl, name, &best, &new, *ret_keyblock) >= 0)
{
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
else
best = new;
new.uid = NULL;
while (getkey_next (ctrl, ctx, &new.key, &new_keyblock) == 0)
{
int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock);
release_kbnode (new_keyblock);
if (diff < 0)
{
/* New key is better. */
release_public_key_parts (&best.key);
free_user_id (best.uid);
best = new;
}
else if (diff > 0)
{
/* Old key is better. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
else
{
/* A tie. Keep the old key. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
new.uid = NULL;
}
getkey_end (ctrl, ctx);
ctx = NULL;
free_user_id (best.uid);
best.uid = NULL;
if (best.valid)
{
ctx = xtrycalloc (1, sizeof **retctx);
if (! ctx)
err = gpg_error_from_syserror ();
else
{
ctx->kr_handle = keydb_new (ctrl);
if (! ctx->kr_handle)
{
err = gpg_error_from_syserror ();
xfree (ctx);
ctx = NULL;
if (retctx)
*retctx = NULL;
}
else
{
u32 *keyid = pk_keyid (&best.key);
ctx->exact = 1;
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx->items[0].u.kid[0] = keyid[0];
ctx->items[0].u.kid[1] = keyid[1];
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
err = getkey_next (ctrl, ctx, NULL, ret_keyblock);
}
}
if (pk)
*pk = best.key;
else
release_public_key_parts (&best.key);
release_public_key_parts (&pk0);
}
else
{
if (pk)
*pk = pk0;
else
release_public_key_parts (&pk0);
}
}
else
{
if (pk)
*pk = pk0;
else
release_public_key_parts (&pk0);
}
if (err && ctx)
{
getkey_end (ctrl, ctx);
ctx = NULL;
}
if (retctx && ctx)
{
*retctx = ctx;
ctx = NULL;
}
leave:
getkey_end (ctrl, ctx);
return err;
}
/* Get a public key from a file.
*
* PK is the buffer to store the key. The caller needs to make sure
* that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to
* the lookup function and is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only
* keys with the specified usage will be returned.
*
* FNAME is the file name. That file should contain exactly one
* keyblock.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key
* is not found.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. The caller must release the content of PK by
* calling release_public_key_parts (or, if PK was malloced, using
* free_public_key).
*/
gpg_error_t
get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname)
{
gpg_error_t err;
kbnode_t keyblock;
kbnode_t found_key;
unsigned int infoflags;
err = read_key_from_file (ctrl, fname, &keyblock);
if (!err)
{
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. FIXME: Check whether this still holds. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, pk->req_usage, 0, 0, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
pk_from_block (pk, keyblock, found_key);
else
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
}
release_kbnode (keyblock);
return err;
}
/* Lookup a key with the specified fingerprint.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: this function does an exact search and thus the
* returned public key may be a subkey rather than the primary key.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If PK->REQ_USAGE is set, it is used to filter the search results.
* (Thus, if PK is not NULL, PK->REQ_USAGE must be valid!!!) See the
* documentation for finish_lookup to understand exactly how this is
* used.
*
* If R_KEYBLOCK is not NULL, then the first result's keyblock is
* returned in *R_KEYBLOCK. This should be freed using
* release_kbnode().
*
* FPRINT is a byte array whose contents is the fingerprint to use as
* the search term. FPRINT_LEN specifies the length of the
* fingerprint (in bytes). Currently, only 16, 20, and 32-byte
* fingerprints are supported.
*
* FIXME: We should replace this with the _byname function. This can
* be done by creating a userID conforming to the unified fingerprint
* style. */
int
get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
const byte * fprint, size_t fprint_len)
{
int rc;
if (r_keyblock)
*r_keyblock = NULL;
if (fprint_len == 32 || fprint_len == 20 || fprint_len == 16)
{
struct getkey_ctx_s ctx;
KBNODE kb = NULL;
KBNODE found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1;
ctx.not_allocated = 1;
/* FIXME: We should get the handle from the cache like we do in
* get_pubkey. */
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_FPR;
memcpy (ctx.items[0].u.fpr, fprint, fprint_len);
ctx.items[0].fprlen = fprint_len;
if (pk)
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc && pk)
pk_from_block (pk, kb, found_key);
if (!rc && r_keyblock)
{
*r_keyblock = kb;
kb = NULL;
}
release_kbnode (kb);
getkey_end (ctrl, &ctx);
}
else
rc = GPG_ERR_GENERAL; /* Oops */
return rc;
}
/* This function is similar to get_pubkey_byfprint, but it doesn't
* merge the self-signed data into the public key and subkeys or into
* the user ids. It also doesn't add the key to the user id cache.
* Further, this function ignores PK->REQ_USAGE.
*
* This function is intended to avoid recursion and, as such, should
* only be used in very specific situations.
*
* Like get_pubkey_byfprint, PK may be NULL. In that case, this
* function effectively just checks for the existence of the key. */
gpg_error_t
get_pubkey_byfprint_fast (ctrl_t ctrl, PKT_public_key * pk,
const byte * fprint, size_t fprint_len)
{
gpg_error_t err;
KBNODE keyblock;
err = get_keyblock_byfprint_fast (ctrl,
&keyblock, NULL, fprint, fprint_len, 0);
if (!err)
{
if (pk)
copy_public_key (pk, keyblock->pkt->pkt.public_key);
release_kbnode (keyblock);
}
return err;
}
/* This function is similar to get_pubkey_byfprint_fast but returns a
* keydb handle at R_HD and the keyblock at R_KEYBLOCK. R_KEYBLOCK or
* R_HD may be NULL. If LOCK is set the handle has been opend in
* locked mode and keydb_disable_caching () has been called. On error
* R_KEYBLOCK is set to NULL but R_HD must be released by the caller;
* it may have a value of NULL, though. This allows to do an insert
* operation on a locked keydb handle. */
gpg_error_t
get_keyblock_byfprint_fast (ctrl_t ctrl,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len, int lock)
{
gpg_error_t err;
KEYDB_HANDLE hd;
kbnode_t keyblock;
byte fprbuf[MAX_FINGERPRINT_LEN];
int i;
if (r_keyblock)
*r_keyblock = NULL;
if (r_hd)
*r_hd = NULL;
for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++)
fprbuf[i] = fprint[i];
hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
if (lock)
{
err = keydb_lock (hd);
if (err)
{
/* If locking did not work, we better don't return a handle
* at all - there was a reason that locking has been
* requested. */
keydb_release (hd);
return err;
}
keydb_disable_caching (hd);
}
- /* Fo all other errors we return the handle. */
+ /* For all other errors we return the handle. */
if (r_hd)
*r_hd = hd;
err = keydb_search_fpr (hd, fprbuf, fprint_len);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
err = keydb_get_keyblock (hd, &keyblock);
if (err)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (err));
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY);
/* Not caching key here since it won't have all of the fields
properly set. */
if (r_keyblock)
*r_keyblock = keyblock;
else
release_kbnode (keyblock);
if (!r_hd)
keydb_release (hd);
return 0;
}
const char *
parse_def_secret_key (ctrl_t ctrl)
{
KEYDB_HANDLE hd = NULL;
strlist_t t;
static int warned;
for (t = opt.def_secret_key; t; t = t->next)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
KBNODE kb;
KBNODE node;
err = classify_user_id (t->d, &desc, 1);
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
t->d, gpg_strerror (err));
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"), "--default-key");
continue;
}
if (! hd)
{
hd = keydb_new (ctrl);
if (!hd)
return NULL;
}
else
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
continue;
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err));
t = NULL;
break;
}
err = keydb_get_keyblock (hd, &kb);
if (err)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (err));
continue;
}
merge_selfsigs (ctrl, kb);
err = gpg_error (GPG_ERR_NO_SECKEY);
node = kb;
do
{
PKT_public_key *pk = node->pkt->pkt.public_key;
/* Check if the key is valid. */
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "revoked");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "expired");
continue;
}
if (pk_is_disabled (pk))
{
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "disabled");
continue;
}
if (agent_probe_secret_key (ctrl, pk))
{
/* This is a valid key. */
err = 0;
break;
}
}
while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY)));
release_kbnode (kb);
if (err)
{
if (! warned && ! opt.quiet)
{
log_info (_("Warning: not using '%s' as default key: %s\n"),
t->d, gpg_strerror (GPG_ERR_NO_SECKEY));
print_reported_error (err, GPG_ERR_NO_SECKEY);
}
}
else
{
if (! warned && ! opt.quiet)
log_info (_("using \"%s\" as default secret key for signing\n"),
t->d);
break;
}
}
if (! warned && opt.def_secret_key && ! t)
log_info (_("all values passed to '%s' ignored\n"),
"--default-key");
warned = 1;
if (hd)
keydb_release (hd);
if (t)
return t->d;
return NULL;
}
/* Look up a secret key.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If --default-key was set, then the specified key is looked up. (In
* this case, the default key is returned even if it is considered
* unusable. See the documentation for skip_unusable for exactly what
* this means.)
*
* Otherwise, this initiates a DB scan that returns all keys that are
* usable (see previous paragraph for exactly what usable means) and
* for which a secret key is available.
*
* This function returns the first match. Additional results can be
* returned using getkey_next. */
gpg_error_t
get_seckey_default (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
strlist_t namelist = NULL;
int include_unusable = 1;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else
include_unusable = 0;
err = key_byname (ctrl, NULL, namelist, pk, 1, include_unusable, NULL, NULL);
free_strlist (namelist);
return err;
}
/* Search for keys matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAMES is not NULL, then a search query is constructed using
* classify_user_id on each of the strings in the list. (Recall: the
* database does an OR of the terms, not an AND.) If NAMES is
* NULL, then all results are returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
gpg_error_t
getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret, kbnode_t *ret_keyblock)
{
return key_byname (ctrl, retctx, names, pk, want_secret, 1,
ret_keyblock, NULL);
}
/* Search for one key matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAME is not NULL, then a search query is constructed using
* classify_user_id on the string. In this case, even unusable keys
* (see the documentation for skip_unusable for an exact definition of
* unusable) are returned. Otherwise, if --default-key was set, then
* that key is returned (even if it is unusable). If neither of these
* conditions holds, then the first usable key is returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found.
*
* FIXME: We also have the get_pubkey_byname function which has a
* different semantic. Should be merged with this one. */
gpg_error_t
getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret, kbnode_t *ret_keyblock)
{
gpg_error_t err;
strlist_t namelist = NULL;
int with_unusable = 1;
const char *def_secret_key = NULL;
if (want_secret && !name)
def_secret_key = parse_def_secret_key (ctrl);
if (want_secret && !name && def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (name)
add_to_strlist (&namelist, name);
else
with_unusable = 0;
err = key_byname (ctrl, retctx, namelist, pk, want_secret, with_unusable,
ret_keyblock, NULL);
/* FIXME: Check that we really return GPG_ERR_NO_SECKEY if
WANT_SECRET has been used. */
free_strlist (namelist);
return err;
}
/* Return the next search result.
*
* If PK is not NULL, the public key of the next result is returned in
* *PK. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xmalloc, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire
* found keyblock is returned which must be released with
* release_kbnode. If the function returns an error NULL is stored at
* RET_KEYBLOCK.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
getkey_next (ctrl_t ctrl, getkey_ctx_t ctx,
PKT_public_key *pk, kbnode_t *ret_keyblock)
{
int rc; /* Fixme: Make sure this is proper gpg_error */
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
/* We need to disable the caching so that for an exact key search we
won't get the result back from the cache and thus end up in an
endless loop. The endless loop can occur, because the cache is
used without respecting the current file pointer! */
keydb_disable_caching (ctx->kr_handle);
/* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the
* caller wants PK, but not RET_KEYBLOCK, we need hand in our own
* keyblock. */
if (pk && ret_keyblock == NULL)
ret_keyblock = &keyblock;
rc = lookup (ctrl, ctx, ctx->want_secret,
ret_keyblock, pk ? &found_key : NULL);
if (!rc && pk)
{
log_assert (found_key);
pk_from_block (pk, NULL, found_key);
release_kbnode (keyblock);
}
return rc;
}
/* Release any resources used by a key listing context. This must be
* called on the context returned by, e.g., getkey_byname. */
void
getkey_end (ctrl_t ctrl, getkey_ctx_t ctx)
{
if (ctx)
{
#ifdef HAVE_W32_SYSTEM
/* FIXME: This creates a big regression for Windows because the
* keyring is only released after the global ctrl is released.
* So if an operation does a getkey and then tries to modify the
* keyring it will fail on Windows with a sharing violation. We
* need to modify all keyring write operations to also take the
* ctrl and close the cached_getkey_kdb handle to make writing
* work. See: GnuPG-bug-id: 3097 */
(void)ctrl;
keydb_release (ctx->kr_handle);
#else /*!HAVE_W32_SYSTEM*/
if (ctrl && !ctrl->cached_getkey_kdb)
ctrl->cached_getkey_kdb = ctx->kr_handle;
else
keydb_release (ctx->kr_handle);
#endif /*!HAVE_W32_SYSTEM*/
free_strlist (ctx->extra_list);
if (!ctx->not_allocated)
xfree (ctx);
}
}
/************************************************
************* Merging stuff ********************
************************************************/
/* Set the mainkey_id fields for all keys in KEYBLOCK. This is
* usually done by merge_selfsigs but at some places we only need the
* main_kid not a full merge. The function also guarantees that all
* pk->keyids are computed. */
void
setup_main_keyids (kbnode_t keyblock)
{
u32 kid[2], mainkid[2];
kbnode_t kbctx, node;
PKT_public_key *pk;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keyid_from_pk (pk, mainkid);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */
if (!pk->main_keyid[0] && !pk->main_keyid[1])
{
pk->main_keyid[0] = mainkid[0];
pk->main_keyid[1] = mainkid[1];
}
}
}
/* KEYBLOCK corresponds to a public key block. This function merges
* much of the information from the self-signed data into the public
* key, public subkey and user id data structures. If you use the
* high-level search API (e.g., get_pubkey) for looking up key blocks,
* then you don't need to call this function. This function is
* useful, however, if you change the keyblock, e.g., by adding or
* removing a self-signed data packet. */
void
merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock)
{
if (!keyblock)
;
else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
merge_selfsigs (ctrl, keyblock);
else
log_debug ("FIXME: merging secret key blocks is not anymore available\n");
}
static int
parse_key_usage (PKT_signature * sig)
{
int key_usage = 0;
const byte *p;
size_t n;
byte flags;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_FLAGS, &n);
if (p && n)
{
/* First octet of the keyflags. */
flags = *p;
if (flags & 1)
{
key_usage |= PUBKEY_USAGE_CERT;
flags &= ~1;
}
if (flags & 2)
{
key_usage |= PUBKEY_USAGE_SIG;
flags &= ~2;
}
/* We do not distinguish between encrypting communications and
encrypting storage. */
if (flags & (0x04 | 0x08))
{
key_usage |= PUBKEY_USAGE_ENC;
flags &= ~(0x04 | 0x08);
}
if (flags & 0x20)
{
key_usage |= PUBKEY_USAGE_AUTH;
flags &= ~0x20;
}
if (flags)
key_usage |= PUBKEY_USAGE_UNKNOWN;
if (!key_usage)
key_usage |= PUBKEY_USAGE_NONE;
}
else if (p) /* Key flags of length zero. */
key_usage |= PUBKEY_USAGE_NONE;
/* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a
capability that we do not handle. This serves to distinguish
between a zero key usage which we handle as the default
capabilities for that algorithm, and a usage that we do not
handle. Likewise we use PUBKEY_USAGE_NONE to indicate that
key_flags have been given but they do not specify any usage. */
return key_usage;
}
/* Apply information from SIGNODE (which is the valid self-signature
* associated with that UID) to the UIDNODE:
* - weather the UID has been revoked
* - assumed creation date of the UID
* - temporary store the keyflags here
* - temporary store the key expiration time here
* - mark whether the primary user ID flag hat been set.
* - store the preferences
*/
static void
fixup_uidnode (KBNODE uidnode, KBNODE signode, u32 keycreated)
{
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p, *sym, *aead, *hash, *zip;
size_t n, nsym, naead, nhash, nzip;
sig->flags.chosen_selfsig = 1;/* We chose this one. */
uid->created = 0; /* Not created == invalid. */
if (IS_UID_REV (sig))
{
uid->flags.revoked = 1;
return; /* Has been revoked. */
}
else
uid->flags.revoked = 0;
uid->expiredate = sig->expiredate;
if (sig->flags.expired)
{
uid->flags.expired = 1;
return; /* Has expired. */
}
else
uid->flags.expired = 0;
uid->created = sig->timestamp; /* This one is okay. */
uid->selfsigversion = sig->version;
/* If we got this far, it's not expired :) */
uid->flags.expired = 0;
/* Store the key flags in the helper variable for later processing. */
uid->help_key_usage = parse_key_usage (sig);
/* Ditto for the key expiration. */
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
uid->help_key_expire = keycreated + buf32_to_u32 (p);
else
uid->help_key_expire = 0;
/* Set the primary user ID flag - we will later wipe out some
* of them to only have one in our keyblock. */
uid->flags.primary = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p)
uid->flags.primary = 2;
/* We could also query this from the unhashed area if it is not in
* the hased area and then later try to decide which is the better
* there should be no security problem with this.
* For now we only look at the hashed one. */
/* Now build the preferences list. These must come from the
hashed section so nobody can modify the ciphers a key is
willing to accept. */
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_SYM, &n);
sym = p;
nsym = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_AEAD, &n);
aead = p;
naead = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_HASH, &n);
hash = p;
nhash = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_COMPR, &n);
zip = p;
nzip = p ? n : 0;
if (uid->prefs)
xfree (uid->prefs);
n = nsym + naead + nhash + nzip;
if (!n)
uid->prefs = NULL;
else
{
uid->prefs = xmalloc (sizeof (*uid->prefs) * (n + 1));
n = 0;
for (; nsym; nsym--, n++)
{
uid->prefs[n].type = PREFTYPE_SYM;
uid->prefs[n].value = *sym++;
}
for (; naead; naead--, n++)
{
uid->prefs[n].type = PREFTYPE_AEAD;
uid->prefs[n].value = *aead++;
}
for (; nhash; nhash--, n++)
{
uid->prefs[n].type = PREFTYPE_HASH;
uid->prefs[n].value = *hash++;
}
for (; nzip; nzip--, n++)
{
uid->prefs[n].type = PREFTYPE_ZIP;
uid->prefs[n].value = *zip++;
}
uid->prefs[n].type = PREFTYPE_NONE; /* End of list marker */
uid->prefs[n].value = 0;
}
/* See whether we have the MDC feature. */
uid->flags.mdc = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x01))
uid->flags.mdc = 1;
/* See whether we have the AEAD feature. */
uid->flags.aead = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x02))
uid->flags.aead = 1;
/* And the keyserver modify flag. */
uid->flags.ks_modify = 1;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KS_FLAGS, &n);
if (p && n && (p[0] & 0x80))
uid->flags.ks_modify = 0;
}
static void
sig_to_revoke_info (PKT_signature * sig, struct revoke_info *rinfo)
{
rinfo->date = sig->timestamp;
rinfo->algo = sig->pubkey_algo;
rinfo->keyid[0] = sig->keyid[0];
rinfo->keyid[1] = sig->keyid[1];
}
/* Given a keyblock, parse the key block and extract various pieces of
* information and save them with the primary key packet and the user
* id packets. For instance, some information is stored in signature
* packets. We find the latest such valid packet (since the user can
* change that information) and copy its contents into the
* PKT_public_key.
*
* Note that R_REVOKED may be set to 0, 1 or 2.
*
* This function fills in the following fields in the primary key's
* keyblock:
*
* main_keyid (computed)
* revkey / numrevkeys (derived from self signed key data)
* flags.valid (whether we have at least 1 self-sig)
* flags.maybe_revoked (whether a designed revoked the key, but
* we are missing the key to check the sig)
* selfsigversion (highest version of any valid self-sig)
* pubkey_usage (derived from most recent self-sig or most
* recent user id)
* has_expired (various sources)
* expiredate (various sources)
*
* See the documentation for fixup_uidnode for how the user id packets
* are modified. In addition to that the primary user id's is_primary
* field is set to 1 and the other user id's is_primary are set to 0.
*/
static void
merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked,
struct revoke_info *rinfo)
{
PKT_public_key *pk = NULL;
KBNODE k;
u32 kid[2];
u32 sigdate, uiddate, uiddate2;
KBNODE signode, uidnode, uidnode2;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
int key_expire_seen = 0;
byte sigversion = 0;
*r_revoked = 0;
memset (rinfo, 0, sizeof (*rinfo));
/* Section 11.1 of RFC 4880 determines the order of packets within a
* message. There are three sections, which must occur in the
* following order: the public key, the user ids and user attributes
* and the subkeys. Within each section, each primary packet (e.g.,
* a user id packet) is followed by one or more signature packets,
* which modify that packet. */
/* According to Section 11.1 of RFC 4880, the public key must be the
first packet. Note that parse_keyblock_image ensures that the
first packet is the public key. */
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keytimestamp = pk->timestamp;
keyid_from_pk (pk, kid);
pk->main_keyid[0] = kid[0];
pk->main_keyid[1] = kid[1];
if (pk->version < 4)
{
/* Before v4 the key packet itself contains the expiration date
* and there was no way to change it, so we start with the one
* from the key packet. */
key_expire = pk->max_expiredate;
key_expire_seen = 1;
}
/* First pass:
*
* - Find the latest direct key self-signature. We assume that the
* newest one overrides all others.
*
* - Determine whether the key has been revoked.
*
* - Gather all revocation keys (unlike other data, we don't just
* take them from the latest self-signed packet).
*
* - Determine max (sig[...]->version).
*/
/* Reset this in case this key was already merged. */
xfree (pk->revkey);
pk->revkey = NULL;
pk->numrevkeys = 0;
signode = NULL;
sigdate = 0; /* Helper variable to find the latest signature. */
/* According to Section 11.1 of RFC 4880, the public key comes first
* and is immediately followed by any signature packets that modify
* it. */
for (k = keyblock;
k && k->pkt->pkttype != PKT_USER_ID
&& k->pkt->pkttype != PKT_ATTRIBUTE
&& k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{ /* Self sig. */
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_KEY_REV (sig))
{
/* Key has been revoked - there is no way to
* override such a revocation, so we theoretically
* can stop now. We should not cope with expiration
* times for revocations here because we have to
* assume that an attacker can generate all kinds of
* signatures. However due to the fact that the key
* has been revoked it does not harm either and by
* continuing we gather some more info on that
* key. */
*r_revoked = 1;
sig_to_revoke_info (sig, rinfo);
}
else if (IS_KEY_SIG (sig))
{
/* Add the indicated revocations keys from all
* signatures not just the latest. We do this
* because you need multiple 1F sigs to properly
* handle revocation keys (PGP does it this way, and
* a revocation key could be sensitive and hence in
* a different signature). */
if (sig->revkey)
{
int i;
pk->revkey =
xrealloc (pk->revkey, sizeof (struct revocation_key) *
(pk->numrevkeys + sig->numrevkeys));
for (i = 0; i < sig->numrevkeys; i++, pk->numrevkeys++)
{
pk->revkey[pk->numrevkeys].class
= sig->revkey[i].class;
pk->revkey[pk->numrevkeys].algid
= sig->revkey[i].algid;
pk->revkey[pk->numrevkeys].fprlen
= sig->revkey[i].fprlen;
memcpy (pk->revkey[pk->numrevkeys].fpr,
sig->revkey[i].fpr, sig->revkey[i].fprlen);
memset (pk->revkey[pk->numrevkeys].fpr
+ sig->revkey[i].fprlen,
0,
sizeof (sig->revkey[i].fpr)
- sig->revkey[i].fprlen);
}
}
if (sig->timestamp >= sigdate)
{ /* This is the latest signature so far. */
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
}
}
/* Remove dupes from the revocation keys. */
if (pk->revkey)
{
int i, j, x, changed = 0;
for (i = 0; i < pk->numrevkeys; i++)
{
for (j = i + 1; j < pk->numrevkeys; j++)
{
if (memcmp (&pk->revkey[i], &pk->revkey[j],
sizeof (struct revocation_key)) == 0)
{
/* remove j */
for (x = j; x < pk->numrevkeys - 1; x++)
pk->revkey[x] = pk->revkey[x + 1];
pk->numrevkeys--;
j--;
changed = 1;
}
}
}
if (changed)
pk->revkey = xrealloc (pk->revkey,
pk->numrevkeys *
sizeof (struct revocation_key));
}
/* SIGNODE is the 1F signature packet with the latest creation time.
* Extract some information from it. */
if (signode)
{
/* Some information from a direct key signature take precedence
* over the same information given in UID sigs. */
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p;
key_usage = parse_key_usage (sig);
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
{
key_expire = keytimestamp + buf32_to_u32 (p);
key_expire_seen = 1;
}
/* Mark that key as valid: One direct key signature should
* render a key as valid. */
pk->flags.valid = 1;
}
/* Pass 1.5: Look for key revocation signatures that were not made
* by the key (i.e. did a revocation key issue a revocation for
* us?). Only bother to do this if there is a revocation key in the
* first place and we're not revoked already. */
if (!*r_revoked && pk->revkey)
for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (IS_KEY_REV (sig) &&
(sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1]))
{
int rc = check_revocation_keys (ctrl, pk, sig);
if (rc == 0)
{
*r_revoked = 2;
sig_to_revoke_info (sig, rinfo);
/* Don't continue checking since we can't be any
* more revoked than this. */
break;
}
else if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
pk->flags.maybe_revoked = 1;
/* A failure here means the sig did not verify, was
* not issued by a revocation key, or a revocation
* key loop was broken. If a revocation key isn't
* findable, however, the key might be revoked and
* we don't know it. */
/* Fixme: In the future handle subkey and cert
* revocations? PGP doesn't, but it's in 2440. */
}
}
}
/* Second pass: Look at the self-signature of all user IDs. */
/* According to RFC 4880 section 11.1, user id and attribute packets
* are in the second section, after the public key packet and before
* the subkey packets. */
signode = uidnode = NULL;
sigdate = 0; /* Helper variable to find the latest signature in one UID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE)
{ /* New user id packet. */
/* Apply the data from the most recent self-signed packet to
* the preceding user id packet. */
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* Clear SIGNODE. The only relevant self-signed data for
* UIDNODE follows it. */
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else
uidnode = NULL;
signode = NULL;
sigdate = 0;
}
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* signature did not verify */
else if ((IS_UID_SIG (sig) || IS_UID_REV (sig))
&& sig->timestamp >= sigdate)
{
/* Note: we allow invalidation of cert revocations
* by a newer signature. An attacker can't use this
* because a key should be revoked with a key revocation.
* The reason why we have to allow for that is that at
* one time an email address may become invalid but later
* the same email address may become valid again (hired,
* fired, hired again). */
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* If the key isn't valid yet, and we have
* --allow-non-selfsigned-uid set, then force it valid. */
if (!pk->flags.valid && opt.allow_non_selfsigned_uid)
{
if (opt.verbose)
log_info (_("Invalid key %s made valid by"
" --allow-non-selfsigned-uid\n"), keystr_from_pk (pk));
pk->flags.valid = 1;
}
/* The key STILL isn't valid, so try and find an ultimately
* trusted signature. */
if (!pk->flags.valid)
{
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1])
{
PKT_public_key *ultimate_pk;
ultimate_pk = xmalloc_clear (sizeof (*ultimate_pk));
/* We don't want to use the full get_pubkey to avoid
* infinite recursion in certain cases. There is no
* reason to check that an ultimately trusted key is
* still valid - if it has been revoked the user
* should also remove the ultimate trust flag. */
if (get_pubkey_fast (ctrl, ultimate_pk, sig->keyid) == 0
&& check_key_signature2 (ctrl,
keyblock, k, ultimate_pk,
NULL, NULL, NULL, NULL) == 0
&& get_ownertrust (ctrl, ultimate_pk) == TRUST_ULTIMATE)
{
free_public_key (ultimate_pk);
pk->flags.valid = 1;
break;
}
free_public_key (ultimate_pk);
}
}
}
}
/* Record the highest selfsig version so we know if this is a v3 key
* through and through, or a v3 key with a v4 selfsig somewhere.
* This is useful in a few places to know if the key must be treated
* as PGP2-style or OpenPGP-style. Note that a selfsig revocation
* with a higher version number will also raise this value. This is
* okay since such a revocation must be issued by the user (i.e. it
* cannot be issued by someone else to modify the key behavior.) */
pk->selfsigversion = sigversion;
/* Now that we had a look at all user IDs we can now get some
* information from those user IDs. */
if (!key_usage)
{
/* Find the latest user ID with key flags set. */
uiddate = 0; /* Helper to find the latest user ID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_usage && uid->created > uiddate)
{
key_usage = uid->help_key_usage;
uiddate = uid->created;
}
}
}
}
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (pk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (pk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
/* Whatever happens, it's a primary key, so it can certify. */
pk->pubkey_usage = key_usage | PUBKEY_USAGE_CERT;
if (!key_expire_seen)
{
/* Find the latest valid user ID with a key expiration set
* Note, that this may be a different one from the above because
* some user IDs may have no expiration date set. */
uiddate = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_expire && uid->created > uiddate)
{
key_expire = uid->help_key_expire;
uiddate = uid->created;
}
}
}
}
/* Currently only v3 keys have a maximum expiration date, but I'll
* bet v5 keys get this feature again. */
if (key_expire == 0
|| (pk->max_expiredate && key_expire > pk->max_expiredate))
key_expire = pk->max_expiredate;
pk->has_expired = key_expire >= curtime ? 0 : key_expire;
pk->expiredate = key_expire;
/* Fixme: we should see how to get rid of the expiretime fields but
* this needs changes at other places too. */
/* And now find the real primary user ID and delete all others. */
uiddate = uiddate2 = 0;
uidnode = uidnode2 = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->flags.primary)
{
if (uid->created > uiddate)
{
uiddate = uid->created;
uidnode = k;
}
else if (uid->created == uiddate && uidnode)
{
/* The dates are equal, so we need to do a different
* (and arbitrary) comparison. This should rarely,
* if ever, happen. It's good to try and guarantee
* that two different GnuPG users with two different
* keyrings at least pick the same primary. */
if (cmp_user_ids (uid, uidnode->pkt->pkt.user_id) > 0)
uidnode = k;
}
}
else
{
if (uid->created > uiddate2)
{
uiddate2 = uid->created;
uidnode2 = k;
}
else if (uid->created == uiddate2 && uidnode2)
{
if (cmp_user_ids (uid, uidnode2->pkt->pkt.user_id) > 0)
uidnode2 = k;
}
}
}
}
if (uidnode)
{
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID &&
!k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (k != uidnode)
uid->flags.primary = 0;
}
}
}
else if (uidnode2)
{
/* None is flagged primary - use the latest user ID we have,
* and disambiguate with the arbitrary packet comparison. */
uidnode2->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* None of our uids were self-signed, so pick the one that
* sorts first to be the primary. This is the best we can do
* here since there are no self sigs to date the uids. */
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data)
{
if (!uidnode)
{
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
continue;
}
else
{
if (cmp_user_ids (k->pkt->pkt.user_id,
uidnode->pkt->pkt.user_id) > 0)
{
uidnode->pkt->pkt.user_id->flags.primary = 0;
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* just to be safe: */
k->pkt->pkt.user_id->flags.primary = 0;
}
}
}
}
}
}
/* Convert a buffer to a signature. Useful for 0x19 embedded sigs.
* Caller must free the signature when they are done. */
static PKT_signature *
buf_to_sig (const byte * buf, size_t len)
{
PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature));
IOBUF iobuf = iobuf_temp_with_content (buf, len);
int save_mode = set_packet_list_mode (0);
if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0)
{
free_seckey_enc (sig);
sig = NULL;
}
set_packet_list_mode (save_mode);
iobuf_close (iobuf);
return sig;
}
/* Use the self-signed data to fill in various fields in subkeys.
*
* KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in.
*
* Sets the following fields on the subkey:
*
* main_keyid
* flags.valid if the subkey has a valid self-sig binding
* flags.revoked
* flags.backsig
* pubkey_usage
* has_expired
* expired_date
*
* On this subkey's most revent valid self-signed packet, the
* following field is set:
*
* flags.chosen_selfsig
*/
static void
merge_selfsigs_subkey (ctrl_t ctrl, kbnode_t keyblock, kbnode_t subnode)
{
PKT_public_key *mainpk = NULL, *subpk = NULL;
PKT_signature *sig;
KBNODE k;
u32 mainkid[2];
u32 sigdate = 0;
KBNODE signode;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
const byte *p;
if (subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY)
BUG ();
mainpk = keyblock->pkt->pkt.public_key;
if (mainpk->version < 4)
return;/* (actually this should never happen) */
keyid_from_pk (mainpk, mainkid);
subpk = subnode->pkt->pkt.public_key;
keytimestamp = subpk->timestamp;
subpk->flags.valid = 0;
subpk->flags.exact = 0;
subpk->main_keyid[0] = mainpk->main_keyid[0];
subpk->main_keyid[1] = mainpk->main_keyid[1];
/* Find the latest key binding self-signature. */
signode = NULL;
sigdate = 0; /* Helper to find the latest signature. */
for (k = subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
sig = k->pkt->pkt.signature;
if (sig->keyid[0] == mainkid[0] && sig->keyid[1] == mainkid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_SUBKEY_REV (sig))
{
/* Note that this means that the date on a
* revocation sig does not matter - even if the
* binding sig is dated after the revocation sig,
* the subkey is still marked as revoked. This
* seems ok, as it is just as easy to make new
* subkeys rather than re-sign old ones as the
* problem is in the distribution. Plus, PGP (7)
* does this the same way. */
subpk->flags.revoked = 1;
sig_to_revoke_info (sig, &subpk->revoked);
/* Although we could stop now, we continue to
* figure out other information like the old expiration
* time. */
}
else if (IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate)
{
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
}
}
}
}
}
/* No valid key binding. */
if (!signode)
return;
sig = signode->pkt->pkt.signature;
sig->flags.chosen_selfsig = 1; /* So we know which selfsig we chose later. */
key_usage = parse_key_usage (sig);
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (subpk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (subpk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
subpk->pubkey_usage = key_usage;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
key_expire = keytimestamp + buf32_to_u32 (p);
else
key_expire = 0;
subpk->has_expired = key_expire >= curtime ? 0 : key_expire;
subpk->expiredate = key_expire;
/* Algo doesn't exist. */
if (openpgp_pk_test_algo (subpk->pubkey_algo))
return;
subpk->flags.valid = 1;
/* Find the most recent 0x19 embedded signature on our self-sig. */
if (!subpk->flags.backsig)
{
int seq = 0;
size_t n;
PKT_signature *backsig = NULL;
sigdate = 0;
/* We do this while() since there may be other embedded
* signatures in the future. We only want 0x19 here. */
while ((p = enum_sig_subpkt (sig, 1, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
seq = 0;
/* It is safe to have this in the unhashed area since the 0x19
* is located on the selfsig for convenience, not security. */
while ((p = enum_sig_subpkt (sig, 0, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
if (backsig)
{
/* At this point, backsig contains the most recent 0x19 sig.
* Let's see if it is good. */
/* 2==valid, 1==invalid, 0==didn't check */
if (check_backsig (mainpk, subpk, backsig) == 0)
subpk->flags.backsig = 2;
else
subpk->flags.backsig = 1;
free_seckey_enc (backsig);
}
}
}
/* Merge information from the self-signatures with the public key,
* subkeys and user ids to make using them more easy.
*
* See documentation for merge_selfsigs_main, merge_selfsigs_subkey
* and fixup_uidnode for exactly which fields are updated. */
static void
merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE k;
int revoked;
struct revoke_info rinfo;
PKT_public_key *main_pk;
prefitem_t *prefs;
unsigned int mdc_feature;
unsigned int aead_feature;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
log_error ("expected public key but found secret key "
"- must stop\n");
/* We better exit here because a public key is expected at
* other places too. FIXME: Figure this out earlier and
* don't get to here at all */
g10_exit (1);
}
BUG ();
}
merge_selfsigs_main (ctrl, keyblock, &revoked, &rinfo);
/* Now merge in the data from each of the subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_selfsigs_subkey (ctrl, keyblock, k);
}
}
main_pk = keyblock->pkt->pkt.public_key;
if (revoked || main_pk->has_expired || !main_pk->flags.valid)
{
/* If the primary key is revoked, expired, or invalid we
* better set the appropriate flags on that key and all
* subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (!main_pk->flags.valid)
pk->flags.valid = 0;
if (revoked && !pk->flags.revoked)
{
pk->flags.revoked = revoked;
memcpy (&pk->revoked, &rinfo, sizeof (rinfo));
}
if (main_pk->has_expired)
{
pk->has_expired = main_pk->has_expired;
if (!pk->expiredate || pk->expiredate > main_pk->expiredate)
pk->expiredate = main_pk->expiredate;
}
}
}
return;
}
/* Set the preference list of all keys to those of the primary real
* user ID. Note: we use these preferences when we don't know by
* which user ID the key has been selected.
* fixme: we should keep atoms of commonly used preferences or
* use reference counting to optimize the preference lists storage.
* FIXME: it might be better to use the intersection of
* all preferences.
* Do a similar thing for the MDC feature flag. */
prefs = NULL;
mdc_feature = aead_feature = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->flags.primary)
{
prefs = k->pkt->pkt.user_id->prefs;
mdc_feature = k->pkt->pkt.user_id->flags.mdc;
aead_feature = k->pkt->pkt.user_id->flags.aead;
break;
}
}
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (pk->prefs)
xfree (pk->prefs);
pk->prefs = copy_prefs (prefs);
pk->flags.mdc = mdc_feature;
pk->flags.aead = aead_feature;
}
}
}
/* See whether the key satisfies any additional requirements specified
* in CTX. If so, return the node of an appropriate key or subkey.
* Otherwise, return NULL if there was no appropriate key.
*
* Note that we do not return a reference, i.e. the result must not be
* freed using 'release_kbnode'.
*
* In case the primary key is not required, select a suitable subkey.
* We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or
* we are in PGP7 mode and PUBKEY_USAGE_SIG is set in
* REQ_USAGE.
*
* If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
* are set in REQ_USAGE, we filter by the key's function. Concretely,
* if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only
* return a key if it is (at least) either a signing or a
* certification key.
*
* If REQ_USAGE is set, then we reject any keys that are not good
* (i.e., valid, not revoked, not expired, etc.). This allows the
* getkey functions to be used for plain key listings.
*
* Sets the matched key's user id field (pk->user_id) to the user id
* that matched the low-level search criteria or NULL.
*
* If R_FLAGS is not NULL set certain flags for more detailed error
* reporting. Used flags are:
*
* - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have
* been revoked.
* - LOOKUP_NOT_SELECTED :: No suitable key found
*
* This function needs to handle several different cases:
*
* 1. No requested usage and no primary key requested
* Examples for this case are that we have a keyID to be used
* for decryption or verification.
* 2. No usage but primary key requested
* This is the case for all functions which work on an
* entire keyblock, e.g. for editing or listing
* 3. Usage and primary key requested
* FIXME
* 4. Usage but no primary key requested
* FIXME
*
*/
static kbnode_t
finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags)
{
kbnode_t k;
/* If WANT_EXACT is set, the key or subkey that actually matched the
low-level search criteria. */
kbnode_t foundk = NULL;
/* The user id (if any) that matched the low-level search criteria. */
PKT_user_id *foundu = NULL;
u32 latest_date;
kbnode_t latest_key;
PKT_public_key *pk;
int req_prim;
u32 curtime = make_timestamp ();
if (r_flags)
*r_flags = 0;
#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT)
req_usage &= USAGE_MASK;
/* Request the primary if we're certifying another key, and also if
* signing data while --pgp7 is on since pgp 7 do
* not understand signatures made by a signing subkey. PGP 8 does. */
req_prim = ((req_usage & PUBKEY_USAGE_CERT)
|| (PGP7 && (req_usage & PUBKEY_USAGE_SIG)));
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* For an exact match mark the primary or subkey that matched the
low-level search criteria. */
if (want_exact)
{
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 1))
{
log_assert (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY);
foundk = k;
pk = k->pkt->pkt.public_key;
pk->flags.exact = 1;
break;
}
}
}
/* Get the user id that matched that low-level search criteria. */
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 2))
{
log_assert (k->pkt->pkttype == PKT_USER_ID);
foundu = k->pkt->pkt.user_id;
break;
}
}
if (DBG_LOOKUP)
log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n",
(ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL),
foundk ? "one" : "all", req_usage);
if (!req_usage)
{
latest_key = foundk ? foundk : keyblock;
goto found;
}
latest_date = 0;
latest_key = NULL;
/* Set LATEST_KEY to the latest (the one with the most recent
* timestamp) good (valid, not revoked, not expired, etc.) subkey.
*
* Don't bother if we are only looking for a primary key or we need
* an exact match and the exact match is not a subkey. */
if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY))
;
else
{
kbnode_t nextk;
int n_subkeys = 0;
int n_revoked_or_expired = 0;
int last_secret_key_avail = 0;
/* Either start a loop or check just this one subkey. */
for (k = foundk ? foundk : keyblock; k; k = nextk)
{
if (foundk)
{
/* If FOUNDK is not NULL, then only consider that exact
key, i.e., don't iterate. */
nextk = NULL;
}
else
nextk = k->next;
if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = k->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tusage does not match: want=%x have=%x\n",
req_usage, pk->pubkey_usage);
continue;
}
n_subkeys++;
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
n_revoked_or_expired++;
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
n_revoked_or_expired++;
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (want_secret)
{
int secret_key_avail = agent_probe_secret_key (NULL, pk);
if (!secret_key_avail)
{
if (DBG_LOOKUP)
log_debug ("\tno secret key\n");
continue;
}
if (secret_key_avail > last_secret_key_avail)
{
/* Use this key. */
last_secret_key_avail = secret_key_avail;
latest_date = 0;
}
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure
that it is used. A better change would be to compare
">=" but that might also change the selected keys and
is as such a more intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = k;
}
}
if (n_subkeys == n_revoked_or_expired && r_flags)
*r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED;
}
/* Check if the primary key is ok (valid, not revoke, not expire,
* matches requested usage) if:
*
* - we didn't find an appropriate subkey and we're not doing an
* exact search,
*
* - we're doing an exact match and the exact match was the
* primary key, or,
*
* - we're just considering the primary key. */
if ((!latest_key && !want_exact) || foundk == keyblock || req_prim)
{
if (DBG_LOOKUP && !foundk && !req_prim)
log_debug ("\tno suitable subkeys found - trying primary\n");
pk = keyblock->pkt->pkt.public_key;
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key usage does not match: "
"want=%x have=%x\n", req_usage, pk->pubkey_usage);
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else /* Okay. */
{
if (DBG_LOOKUP)
log_debug ("\tprimary key may be used\n");
latest_key = keyblock;
}
}
if (!latest_key)
{
if (DBG_LOOKUP)
log_debug ("\tno suitable key found - giving up\n");
if (r_flags)
*r_flags |= LOOKUP_NOT_SELECTED;
return NULL; /* Not found. */
}
found:
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n",
(ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL));
if (latest_key)
{
pk = latest_key->pkt->pkt.public_key;
free_user_id (pk->user_id);
pk->user_id = scopy_user_id (foundu);
}
if (latest_key != keyblock && opt.verbose)
{
char *tempkeystr =
xstrdup (keystr_from_pk (latest_key->pkt->pkt.public_key));
log_info (_("using subkey %s instead of primary key %s\n"),
tempkeystr, keystr_from_pk (keyblock->pkt->pkt.public_key));
xfree (tempkeystr);
}
cache_put_keyblock (keyblock);
return latest_key ? latest_key : keyblock; /* Found. */
}
/* Print a KEY_CONSIDERED status line. */
static void
print_status_key_considered (kbnode_t keyblock, unsigned int flags)
{
char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
kbnode_t node;
char flagbuf[20];
if (!is_status_enabled ())
return;
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
break;
if (!node)
{
log_error ("%s: keyblock w/o primary key\n", __func__);
return;
}
hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
snprintf (flagbuf, sizeof flagbuf, " %u", flags);
write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL);
}
/* A high-level function to lookup keys.
*
* This function builds on top of the low-level keydb API. It first
* searches the database using the description stored in CTX->ITEMS,
* then it filters the results using CTX and, finally, if WANT_SECRET
* is set, it ignores any keys for which no secret key is available.
*
* Unlike the low-level search functions, this function also merges
* all of the self-signed data into the keys, subkeys and user id
* packets (see the merge_selfsigs for details).
*
* On success the key's keyblock is stored at *RET_KEYBLOCK, and the
* specific subkey is stored at *RET_FOUND_KEY. Note that we do not
* return a reference in *RET_FOUND_KEY, i.e. the result must not be
* freed using 'release_kbnode', and it is only valid until
* *RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not
* NULL, then RET_KEYBLOCK must not be NULL. */
static int
lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key)
{
int rc;
int no_suitable_key = 0;
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
unsigned int infoflags;
log_assert (ret_found_key == NULL || ret_keyblock != NULL);
if (ret_keyblock)
*ret_keyblock = NULL;
for (;;)
{
rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL);
if (rc)
break;
/* If we are iterating over the entire database, then we need to
* change from KEYDB_SEARCH_MODE_FIRST, which does an implicit
* reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next record. */
if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST)
ctx->items->mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_keyblock (ctx->kr_handle, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto skip;
}
if (want_secret)
{
rc = agent_probe_any_secret_key (NULL, keyblock);
if (gpg_err_code(rc) == GPG_ERR_NO_SECKEY)
goto skip; /* No secret key available. */
if (rc)
goto found; /* Unexpected error. */
}
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact,
want_secret, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
{
no_suitable_key = 0;
goto found;
}
else
{
no_suitable_key = 1;
}
skip:
/* Release resources and continue search. */
release_kbnode (keyblock);
keyblock = NULL;
/* The keyblock cache ignores the current "file position".
* Thus, if we request the next result and the cache matches
* (and it will since it is what we just looked for), we'll get
* the same entry back! We can avoid this infinite loop by
* disabling the cache. */
keydb_disable_caching (ctx->kr_handle);
}
found:
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
if (!rc)
{
if (ret_keyblock)
{
*ret_keyblock = keyblock; /* Return the keyblock. */
keyblock = NULL;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key)
rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY;
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
if (ret_found_key)
{
if (! rc)
*ret_found_key = found_key;
else
*ret_found_key = NULL;
}
return rc;
}
gpg_error_t
get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk,
const byte *fpr_card, size_t fpr_len)
{
gpg_error_t err;
strlist_t namelist = NULL;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (fpr_card)
return get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len);
if (!fpr_card
|| (def_secret_key && def_secret_key[strlen (def_secret_key)-1] == '!'))
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, NULL, NULL);
else
{ /* Default key is specified and card key is also available. */
kbnode_t k, keyblock = NULL;
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, &keyblock, NULL);
if (!err)
for (k = keyblock; k; k = k->next)
{
PKT_public_key *pk_candidate;
char fpr[MAX_FINGERPRINT_LEN];
if (k->pkt->pkttype != PKT_PUBLIC_KEY
&&k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk_candidate = k->pkt->pkt.public_key;
if (!pk_candidate->flags.valid)
continue;
if (!((pk_candidate->pubkey_usage & USAGE_MASK) & pk->req_usage))
continue;
fingerprint_from_pk (pk_candidate, fpr, NULL);
if (!memcmp (fpr_card, fpr, fpr_len))
{
release_public_key_parts (pk);
copy_public_key (pk, pk_candidate);
break;
}
}
release_kbnode (keyblock);
}
free_strlist (namelist);
return err;
}
/*********************************************
*********** User ID printing helpers *******
*********************************************/
/* Return a string with a printable representation of the user_id.
* this string must be freed by xfree. If R_NOUID is not NULL it is
* set to true if a user id was not found; otherwise to false. */
static char *
get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode)
{
char *name;
unsigned int namelen;
char *p;
log_assert (mode != 2);
name = cache_get_uid_bykid (keyid, &namelen);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey (ctrl, NULL, keyid))
name = cache_get_uid_bykid (keyid, &namelen);
}
if (name)
{
if (mode)
p = xasprintf ("%08lX%08lX %.*s",
(ulong) keyid[0], (ulong) keyid[1], namelen, name);
else
p = xasprintf ("%s %.*s", keystr (keyid), namelen, name);
xfree (name);
}
else
{
if (mode)
p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]);
else
p = xasprintf ("%s [?]", keystr (keyid));
}
return p;
}
char *
get_user_id_string_native (ctrl_t ctrl, u32 * keyid)
{
char *p = get_user_id_string (ctrl, keyid, 0);
char *p2 = utf8_to_native (p, strlen (p), 0);
xfree (p);
return p2;
}
char *
get_long_user_id_string (ctrl_t ctrl, u32 * keyid)
{
return get_user_id_string (ctrl, keyid, 1);
}
/* Please try to use get_user_byfpr instead of this one. */
char *
get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid)
{
char *name;
unsigned int namelen;
if (r_nouid)
*r_nouid = 0;
name = cache_get_uid_bykid (keyid, &namelen);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey (ctrl, NULL, keyid))
name = cache_get_uid_bykid (keyid, &namelen);
}
if (!name)
{
name = xstrdup (user_id_not_found_utf8 ());
namelen = strlen (name);
if (r_nouid)
*r_nouid = 1;
}
if (rn && name)
*rn = namelen;
return name;
}
/* Please try to use get_user_id_byfpr_native instead of this one. */
char *
get_user_id_native (ctrl_t ctrl, u32 *keyid)
{
size_t rn;
char *p = get_user_id (ctrl, keyid, &rn, NULL);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the user id for a key designated by its fingerprint, FPR,
which must be MAX_FINGERPRINT_LEN bytes in size. Note: the
returned string, which must be freed using xfree, may not be NUL
terminated. To determine the length of the string, you must use
*RN. */
static char *
get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t fprlen, size_t *rn)
{
char *name;
name = cache_get_uid_byfpr (fpr, fprlen, rn);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey_byfprint (ctrl, NULL, NULL, fpr, fprlen))
name = cache_get_uid_byfpr (fpr, fprlen, rn);
}
if (!name)
{
name = xstrdup (user_id_not_found_utf8 ());
*rn = strlen (name);
}
return name;
}
/* Like get_user_id_byfpr, but convert the string to the native
encoding. The returned string needs to be freed. Unlike
get_user_id_byfpr, the returned string is NUL terminated. */
char *
get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr, size_t fprlen)
{
size_t rn;
char *p = get_user_id_byfpr (ctrl, fpr, fprlen, &rn);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE
get_ctx_handle (GETKEY_CTX ctx)
{
return ctx->kr_handle;
}
static void
free_akl (struct akl *akl)
{
if (! akl)
return;
if (akl->spec)
free_keyserver_spec (akl->spec);
xfree (akl);
}
void
release_akl (void)
{
while (opt.auto_key_locate)
{
struct akl *akl2 = opt.auto_key_locate;
opt.auto_key_locate = opt.auto_key_locate->next;
free_akl (akl2);
}
}
/* Returns true if the AKL is empty or has only the local method
* active. */
int
akl_empty_or_only_local (void)
{
struct akl *akl;
int any = 0;
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT && akl->type != AKL_LOCAL)
{
any = 1;
break;
}
return !any;
}
/* Returns false on error. */
int
parse_auto_key_locate (const char *options_arg)
{
char *tok;
char *options, *options_buf;
options = options_buf = xstrdup (options_arg);
while ((tok = optsep (&options)))
{
struct akl *akl, *check, *last = NULL;
int dupe = 0;
if (tok[0] == '\0')
continue;
akl = xmalloc_clear (sizeof (*akl));
if (ascii_strcasecmp (tok, "clear") == 0)
{
xfree (akl);
free_akl (opt.auto_key_locate);
opt.auto_key_locate = NULL;
continue;
}
else if (ascii_strcasecmp (tok, "nodefault") == 0)
akl->type = AKL_NODEFAULT;
else if (ascii_strcasecmp (tok, "local") == 0)
akl->type = AKL_LOCAL;
else if (ascii_strcasecmp (tok, "ldap") == 0)
akl->type = AKL_LDAP;
else if (ascii_strcasecmp (tok, "keyserver") == 0)
akl->type = AKL_KEYSERVER;
else if (ascii_strcasecmp (tok, "cert") == 0)
akl->type = AKL_CERT;
else if (ascii_strcasecmp (tok, "pka") == 0)
akl->type = AKL_PKA;
else if (ascii_strcasecmp (tok, "dane") == 0)
akl->type = AKL_DANE;
else if (ascii_strcasecmp (tok, "wkd") == 0)
akl->type = AKL_WKD;
else if ((akl->spec = parse_keyserver_uri (tok, 1)))
akl->type = AKL_SPEC;
else
{
free_akl (akl);
xfree (options_buf);
return 0;
}
/* We must maintain the order the user gave us */
for (check = opt.auto_key_locate; check;
last = check, check = check->next)
{
/* Check for duplicates */
if (check->type == akl->type
&& (akl->type != AKL_SPEC
|| (akl->type == AKL_SPEC
&& strcmp (check->spec->uri, akl->spec->uri) == 0)))
{
dupe = 1;
free_akl (akl);
break;
}
}
if (!dupe)
{
if (last)
last->next = akl;
else
opt.auto_key_locate = akl;
}
}
xfree (options_buf);
return 1;
}
/* The list of key origins. */
static struct {
const char *name;
int origin;
} key_origin_list[] =
{
{ "self", KEYORG_SELF },
{ "file", KEYORG_FILE },
{ "url", KEYORG_URL },
{ "wkd", KEYORG_WKD },
{ "dane", KEYORG_DANE },
{ "ks-pref", KEYORG_KS_PREF },
{ "ks", KEYORG_KS },
{ "unknown", KEYORG_UNKNOWN }
};
/* Parse the argument for --key-origin. Return false on error. */
int
parse_key_origin (char *string)
{
int i;
char *comma;
comma = strchr (string, ',');
if (comma)
*comma = 0;
if (!ascii_strcasecmp (string, "help"))
{
log_info (_("valid values for option '%s':\n"), "--key-origin");
for (i=0; i < DIM (key_origin_list); i++)
log_info (" %s\n", key_origin_list[i].name);
g10_exit (1);
}
for (i=0; i < DIM (key_origin_list); i++)
if (!ascii_strcasecmp (string, key_origin_list[i].name))
{
opt.key_origin = key_origin_list[i].origin;
xfree (opt.key_origin_url);
opt.key_origin_url = NULL;
if (comma && comma[1])
{
opt.key_origin_url = xstrdup (comma+1);
trim_spaces (opt.key_origin_url);
}
return 1;
}
if (comma)
*comma = ',';
return 0;
}
/* Return a string or "?" for the key ORIGIN. */
const char *
key_origin_string (int origin)
{
int i;
for (i=0; i < DIM (key_origin_list); i++)
if (key_origin_list[i].origin == origin)
return key_origin_list[i].name;
return "?";
}
/* Returns true if a secret key is available for the public key with
key id KEYID; returns false if not. This function ignores legacy
keys. Note: this is just a fast check and does not tell us whether
the secret key is valid; this check merely indicates whether there
is some secret key with the specified key id. */
int
have_secret_key_with_kid (ctrl_t ctrl, u32 *keyid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock;
kbnode_t node;
int result = 0;
kdbhd = keydb_new (ctrl);
if (!kdbhd)
return 0;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = keyid[0];
desc.u.kid[1] = keyid[1];
while (!result)
{
err = keydb_search (kdbhd, &desc, 1, NULL);
if (err)
break;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
break;
}
for (node = keyblock; node; node = node->next)
{
/* Bit 0 of the flags is set if the search found the key
using that key or subkey. Note: a search will only ever
match a single key or subkey. */
if ((node->flag & 1))
{
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
result = 1; /* Secret key available. */
else
result = 0;
break;
}
}
release_kbnode (keyblock);
}
keydb_release (kdbhd);
return result;
}
diff --git a/g10/import.c b/g10/import.c
index 5b50b722b..ab825085e 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -1,4538 +1,4538 @@
/* import.c - import a key into our key storage.
* Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016, 2017, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "trustdb.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/recsel.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/membuf.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "key-check.h"
#include "key-clean.h"
struct import_stats_s
{
ulong count;
ulong no_user_id;
ulong imported;
ulong n_uids;
ulong n_sigs;
ulong n_subk;
ulong unchanged;
ulong n_revoc;
ulong secret_read;
ulong secret_imported;
ulong secret_dups;
ulong skipped_new_keys;
ulong not_imported;
ulong n_sigs_cleaned;
ulong n_uids_cleaned;
ulong v3keys; /* Number of V3 keys seen. */
};
/* Node flag to indicate that a user ID or a subkey has a
* valid self-signature. */
#define NODE_GOOD_SELFSIG 1
/* Node flag to indicate that a user ID or subkey has
* an invalid self-signature. */
#define NODE_BAD_SELFSIG 2
/* Node flag to indicate that the node shall be deleted. */
#define NODE_DELETION_MARK 4
/* A node flag used to temporary mark a node. */
#define NODE_FLAG_A 8
/* A flag used by transfer_secret_keys. */
#define NODE_TRANSFER_SECKEY 16
/* An object and a global instance to store selectors created from
* --import-filter keep-uid=EXPR.
* --import-filter drop-sig=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now. For now we use save and restore
* function to temporary change them.
*/
/* Definition of the import filters. */
struct import_filter_s
{
recsel_expr_t keep_uid;
recsel_expr_t drop_sig;
};
/* The current instance. */
struct import_filter_s import_filter;
static int import (ctrl_t ctrl,
IOBUF inp, const char* fname, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url);
static int read_block (IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys);
static void revocation_present (ctrl_t ctrl, kbnode_t keyblock);
static gpg_error_t import_one (ctrl_t ctrl,
kbnode_t keyblock,
struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len,
unsigned int options, int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid);
static gpg_error_t import_matching_seckeys (
ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch);
static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic);
static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats);
static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
int *non_self);
static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock,
u32 *keyid, unsigned int options);
static int any_uid_left (kbnode_t keyblock);
static int remove_all_uids (kbnode_t *keyblock);
static void remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid);
static int merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig,
kbnode_t keyblock, u32 *keyid,
u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk );
static gpg_error_t append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node,
u32 curtime, int origin, const char *url,
int *n_sigs);
static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static void
release_import_filter (import_filter_t filt)
{
recsel_release (filt->keep_uid);
filt->keep_uid = NULL;
recsel_release (filt->drop_sig);
filt->drop_sig = NULL;
}
static void
cleanup_import_globals (void)
{
release_import_filter (&import_filter);
}
int
parse_import_options(char *str,unsigned int *options,int noisy)
{
struct parse_options import_opts[]=
{
{"import-local-sigs",IMPORT_LOCAL_SIGS,NULL,
N_("import signatures that are marked as local-only")},
{"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,
N_("repair damage from the pks keyserver during import")},
{"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL,
N_("do not clear the ownertrust values during import")},
{"fast-import",IMPORT_FAST,NULL,
N_("do not update the trustdb after import")},
{"import-show",IMPORT_SHOW,NULL,
N_("show key during import")},
{"merge-only",IMPORT_MERGE_ONLY,NULL,
N_("only accept updates to existing keys")},
{"import-clean",IMPORT_CLEAN,NULL,
N_("remove unusable parts from key after import")},
{"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL,
N_("remove as much as possible from key after import")},
{"import-drop-uids", IMPORT_DROP_UIDS, NULL,
N_("do not import user id or attribute packets")},
{"self-sigs-only", IMPORT_SELF_SIGS_ONLY, NULL,
N_("ignore key-signatures which are not self-signatures")},
{"import-export", IMPORT_EXPORT, NULL,
N_("run import filters and export key immediately")},
{"restore", IMPORT_RESTORE, NULL,
N_("assume the GnuPG key backup format")},
{"import-restore", IMPORT_RESTORE, NULL, NULL},
{"repair-keys", IMPORT_REPAIR_KEYS, NULL,
N_("repair keys on import")},
/* No description to avoid string change: Fixme for 2.3 */
{"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL,
NULL},
/* Aliases for backward compatibility */
{"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL},
{"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL},
/* dummy */
{"import-unusable-sigs",0,NULL,NULL},
{"import-clean-sigs",0,NULL,NULL},
{"import-clean-uids",0,NULL,NULL},
{"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to
the new design. */
{NULL,0,NULL,NULL}
};
int rc;
rc = parse_options (str, options, import_opts, noisy);
if (rc && (*options & IMPORT_RESTORE))
{
/* Alter other options we want or don't want for restore. */
*options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST);
*options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_MERGE_ONLY);
}
return rc;
}
/* Parse and set an import filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is all called
* several times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be imported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*/
gpg_error_t
parse_and_set_import_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_import_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&import_filter.keep_uid, string+9);
else if (!strncmp (string, "drop-sig=", 9))
err = recsel_parse_expr (&import_filter.drop_sig, string+9);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Save the current import filters, return them, and clear the current
* filters. Returns NULL on error and sets ERRNO. */
import_filter_t
save_and_clear_import_filter (void)
{
import_filter_t filt;
filt = xtrycalloc (1, sizeof *filt);
if (!filt)
return NULL;
*filt = import_filter;
memset (&import_filter, 0, sizeof import_filter);
return filt;
}
/* Release the current import filters and restore them from NEWFILT.
* Ownership of NEWFILT is moved to this function. */
void
restore_import_filter (import_filter_t filt)
{
if (filt)
{
release_import_filter (&import_filter);
import_filter = *filt;
xfree (filt);
}
}
import_stats_t
import_new_stats_handle (void)
{
return xmalloc_clear ( sizeof (struct import_stats_s) );
}
void
import_release_stats_handle (import_stats_t p)
{
xfree (p);
}
/* Read a key from a file. Only the first key in the file is
* considered and stored at R_KEYBLOCK. FNAME is the name of the
* file.
*/
gpg_error_t
read_key_from_file (ctrl_t ctrl, const char *fname, kbnode_t *r_keyblock)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL;
u32 keyid[2];
int v3keys; /* Dummy */
int non_self; /* Dummy */
(void)ctrl;
*r_keyblock = NULL;
inp = iobuf_open (fname);
if (!inp)
err = gpg_error_from_syserror ();
else if (is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
err = gpg_error (GPG_ERR_EPERM);
}
else
err = 0;
if (err)
{
log_error (_("can't open '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Push the armor filter. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
/* Read the first non-v3 keyblock. */
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
break;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
release_kbnode (keyblock);
keyblock = NULL;
}
if (err)
{
if (gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
goto leave;
}
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
if (!find_next_kbnode (keyblock, PKT_USER_ID))
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
collapse_uids (&keyblock);
clear_kbnode_flags (keyblock);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
{
err = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (!delete_inv_parts (ctrl, keyblock, keyid, 0) )
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
leave:
if (inp)
{
iobuf_close (inp);
/* Must invalidate that ugly cache to actually close the file. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
}
release_kbnode (keyblock);
/* FIXME: Do we need to free PENDING_PKT ? */
return err;
}
/*
* Import the public keys from the given filename. Input may be armored.
* This function rejects all keys which are not validly self signed on at
* least one userid. Only user ids which are self signed will be imported.
* Other signatures are not checked.
*
* Actually this function does a merge. It works like this:
*
* - get the keyblock
* - check self-signatures and remove all userids and their signatures
* without/invalid self-signatures.
* - reject the keyblock, if we have no valid userid.
* - See whether we have this key already in one of our pubrings.
* If not, simply add it to the default keyring.
* - Compare the key and the self-signatures of the new and the one in
* our keyring. If they are different something weird is going on;
* ask what to do.
* - See whether we have only non-self-signature on one user id; if not
* ask the user what to do.
* - compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* (consider looking at the timestamp and use the newest?)
* - Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* - Proceed with next signature.
*
* Key revocation certificates have special handling.
*/
static gpg_error_t
import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
int i;
gpg_error_t err = 0;
struct import_stats_s *stats = stats_handle;
if (!stats)
stats = import_new_stats_handle ();
if (inp)
{
err = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
}
else
{
if (!fnames && !nnames)
nnames = 1; /* Ohh what a ugly hack to jump into the loop */
for (i=0; i < nnames; i++)
{
const char *fname = fnames? fnames[i] : NULL;
IOBUF inp2 = iobuf_open(fname);
if (!fname)
fname = "[stdin]";
if (inp2 && is_secured_file (iobuf_get_fd (inp2)))
{
iobuf_close (inp2);
inp2 = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp2)
log_error (_("can't open '%s': %s\n"), fname, strerror (errno));
else
{
err = import (ctrl, inp2, fname, stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp2);
/* Must invalidate that ugly cache to actually close it. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
if (err)
log_error ("import from '%s' failed: %s\n",
fname, gpg_strerror (err) );
}
if (!fname)
break;
}
}
if (!stats_handle)
{
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))
!= (IMPORT_SHOW | IMPORT_DRY_RUN))
import_print_stats (stats);
import_release_stats_handle (stats);
}
/* If no fast import and the trustdb is dirty (i.e. we added a key
or userID that had something other than a selfsig, a signature
that was other than a selfsig, or any revocation), then
update/check the trustdb if the user specified by setting
interactive or by not setting no-auto-check-trustdb */
if (!(options & IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
void
import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_handle, unsigned int options,
int origin, const char *url)
{
import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle,
NULL, NULL, options, NULL, NULL, origin, url);
}
gpg_error_t
import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
gpg_error_t err;
iobuf_t inp;
inp = iobuf_esopen (fp, "rb", 1, 0);
if (!inp)
{
err = gpg_error_from_syserror ();
log_error ("iobuf_esopen failed: %s\n", gpg_strerror (err));
return err;
}
err = import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp);
return err;
}
static int
import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
unsigned char **fpr,size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */
int rc = 0;
int v3keys;
getkey_disable_caches ();
if (!opt.no_armor) /* Armored reading is not disabled. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
while (!(rc = read_block (inp, options, &pending_pkt, &keyblock, &v3keys)))
{
stats->v3keys += v3keys;
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
{
rc = import_one (ctrl, keyblock,
stats, fpr, fpr_len, options, 0, 0,
screener, screener_arg, origin, url, NULL);
if (secattic)
{
byte tmpfpr[MAX_FINGERPRINT_LEN];
size_t tmpfprlen;
if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
/* Kudge for PGP desktop - see below. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key,
tmpfpr, &tmpfprlen);
rc = import_matching_seckeys (ctrl, secattic,
tmpfpr, tmpfprlen,
stats, opt.batch);
}
release_kbnode (secattic);
secattic = NULL;
}
}
else if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
release_kbnode (secattic);
secattic = NULL;
rc = import_secret_one (ctrl, keyblock, stats,
opt.batch, options, 0,
screener, screener_arg, &secattic);
keyblock = NULL; /* Ownership was transferred. */
if (secattic)
{
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
rc = 0; /* Try import after the next pubkey. */
/* The attic is a workaround for the peculiar PGP
* Desktop method of exporting a secret key: The
* exported file is the concatenation of two armored
* keyblocks; first the private one and then the public
* one. The strange thing is that the secret one has no
* binding signatures at all and thus we have not
* imported it. The attic stores that secret keys and
* we try to import it once after the very next public
* keyblock. */
}
}
else if (keyblock->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (keyblock->pkt->pkt.signature) )
{
release_kbnode (secattic);
secattic = NULL;
rc = import_revoke_cert (ctrl, keyblock, options, stats);
}
else
{
release_kbnode (secattic);
secattic = NULL;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
}
release_kbnode (keyblock);
/* fixme: we should increment the not imported counter but
this does only make sense if we keep on going despite of
errors. For now we do this only if the imported key is too
large. */
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
stats->not_imported++;
}
else if (rc)
break;
if (!(++stats->count % 100) && !opt.quiet)
log_info (_("%lu keys processed so far\n"), stats->count );
if (origin == KEYORG_WKD && stats->count >= 5)
{
/* We limit the number of keys _received_ from the WKD to 5.
* In fact there should be only one key but some sites want
* to store a few expired keys there also. gpg's key
* selection will later figure out which key to use. Note
* that for WKD we always return the fingerprint of the
* first imported key. */
log_info ("import from WKD stopped after %d keys\n", 5);
break;
}
}
stats->v3keys += v3keys;
if (rc == -1)
rc = 0;
else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc));
release_kbnode (secattic);
/* When read_block loop was stopped by error, we have PENDING_PKT left. */
if (pending_pkt)
{
free_packet (pending_pkt, NULL);
xfree (pending_pkt);
}
return rc;
}
/* Helper to migrate secring.gpg to GnuPG 2.1. */
gpg_error_t
import_old_secring (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
struct import_stats_s *stats;
int v3keys;
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
getkey_disable_caches();
stats = import_new_stats_handle ();
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1,
NULL, NULL, NULL);
keyblock = NULL; /* Ownership was transferred. */
}
release_kbnode (keyblock);
if (err)
break;
}
import_release_stats_handle (stats);
if (err == -1)
err = 0;
else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
else if (err)
log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err));
iobuf_close (inp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
return err;
}
void
import_print_stats (import_stats_t stats)
{
if (!opt.quiet)
{
log_info(_("Total number processed: %lu\n"),
stats->count + stats->v3keys);
if (stats->v3keys)
log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys);
if (stats->skipped_new_keys )
log_info(_(" skipped new keys: %lu\n"),
stats->skipped_new_keys );
if (stats->no_user_id )
log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id );
if (stats->imported)
{
log_info(_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged )
log_info(_(" unchanged: %lu\n"), stats->unchanged );
if (stats->n_uids )
log_info(_(" new user IDs: %lu\n"), stats->n_uids );
if (stats->n_subk )
log_info(_(" new subkeys: %lu\n"), stats->n_subk );
if (stats->n_sigs )
log_info(_(" new signatures: %lu\n"), stats->n_sigs );
if (stats->n_revoc )
log_info(_(" new key revocations: %lu\n"), stats->n_revoc );
if (stats->secret_read )
log_info(_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported )
log_info(_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups )
log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported )
log_info(_(" not imported: %lu\n"), stats->not_imported );
if (stats->n_sigs_cleaned)
log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned);
if (stats->n_uids_cleaned)
log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned);
}
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf,
"%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count + stats->v3keys,
stats->no_user_id,
stats->imported,
stats->unchanged,
stats->n_uids,
stats->n_subk,
stats->n_sigs,
stats->n_revoc,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
stats->skipped_new_keys,
stats->not_imported,
stats->v3keys );
write_status_text (STATUS_IMPORT_RES, buf);
}
}
/* Return true if PKTTYPE is valid in a keyblock. */
static int
valid_keyblock_packet (int pkttype)
{
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
return 1;
default:
return 0;
}
}
/* Read the next keyblock from stream A. Meta data (ring trust
* packets) are only considered if OPTIONS has the IMPORT_RESTORE flag
* set. PENDING_PKT should be initialized to NULL and not changed by
* the caller.
*
* Returns 0 for okay, -1 no more blocks, or any other errorcode. The
* integer at R_V3KEY counts the number of unsupported v3 keyblocks.
*/
static int
read_block( IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys)
{
int rc;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t root = NULL;
kbnode_t lastnode = NULL;
int in_cert, in_v3key, skip_sigs;
u32 keyid[2];
int got_keyid = 0;
unsigned int dropped_nonselfsigs = 0;
*r_v3keys = 0;
if (*pending_pkt)
{
root = lastnode = new_kbnode( *pending_pkt );
*pending_pkt = NULL;
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY
|| root->pkt->pkttype == PKT_SECRET_KEY);
in_cert = 1;
keyid_from_pk (root->pkt->pkt.public_key, keyid);
got_keyid = 1;
}
else
in_cert = 0;
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
if (!(options & IMPORT_RESTORE))
parsectx.skip_meta = 1;
in_v3key = 0;
skip_sigs = 0;
while ((rc=parse_packet (&parsectx, pkt)) != -1)
{
if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
&& (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)))
{
in_v3key = 1;
++*r_v3keys;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
else if (rc ) /* (ignore errors) */
{
skip_sigs = 0;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET)
; /* Do not show a diagnostic. */
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_USER_ID
|| pkt->pkttype == PKT_ATTRIBUTE))
{
/* This indicates a too large user id or attribute
* packet. We skip this packet and all following
* signatures. Sure, this won't allow to repair a
* garbled keyring in case one of the signatures belong
* to another user id. However, this better mitigates
* DoS using inserted user ids. */
skip_sigs = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_OLD_COMMENT
|| pkt->pkttype == PKT_COMMENT))
; /* Ignore too large comment packets. */
else
{
log_error("read_block: read error: %s\n", gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
goto ready;
}
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
if (skip_sigs)
{
if (pkt->pkttype == PKT_SIGNATURE)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
skip_sigs = 0;
}
if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
in_v3key = 0;
if (!root && pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (pkt->pkt.signature) )
{
/* This is a revocation certificate which is handled in a
* special way. */
root = new_kbnode( pkt );
pkt = NULL;
goto ready;
}
/* Make a linked list of all packets. */
switch (pkt->pkttype)
{
case PKT_COMPRESSED:
if (check_compress_algo (pkt->pkt.compressed->algorithm))
{
rc = GPG_ERR_COMPR_ALGO;
goto ready;
}
else
{
compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx );
pkt->pkt.compressed->buf = NULL;
if (push_compress_filter2 (a, cfx,
pkt->pkt.compressed->algorithm, 1))
xfree (cfx); /* e.g. in case of compression_algo NONE. */
}
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_RING_TRUST:
/* Skip those packets unless we are in restore mode. */
if ((opt.import_options & IMPORT_RESTORE))
goto x_default;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_SIGNATURE:
if (!in_cert)
goto x_default;
if (!(options & IMPORT_SELF_SIGS_ONLY))
goto x_default;
log_assert (got_keyid);
if (pkt->pkt.signature->keyid[0] == keyid[0]
&& pkt->pkt.signature->keyid[1] == keyid[1])
{ /* This is likely a self-signature. We import this one.
* Eventually we should use the ISSUER_FPR to compare
* self-signatures, but that will work only for v5 keys
* which are currently not even deployed.
* Note that we do not do any crypto verify here because
* that would defeat this very mitigation of DoS by
* importing a key with a huge amount of faked
* key-signatures. A verification will be done later in
* the processing anyway. Here we want a cheap an early
* way to drop non-self-signatures. */
goto x_default;
}
/* Skip this signature. */
dropped_nonselfsigs++;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
if (!got_keyid)
{
keyid_from_pk (pkt->pkt.public_key, keyid);
got_keyid = 1;
}
if (in_cert) /* Store this packet. */
{
*pending_pkt = pkt;
pkt = NULL;
goto ready;
}
in_cert = 1;
goto x_default;
default:
x_default:
if (in_cert && valid_keyblock_packet (pkt->pkttype))
{
if (!root )
root = lastnode = new_kbnode (pkt);
else
{
lastnode->next = new_kbnode (pkt);
lastnode = lastnode->next;
}
pkt = xmalloc (sizeof *pkt);
}
else
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
}
}
ready:
if (rc == -1 && root )
rc = 0;
if (rc )
release_kbnode( root );
else
*ret_root = root;
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree( pkt );
if (!rc && dropped_nonselfsigs && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped_nonselfsigs);
return rc;
}
/* Walk through the subkeys on a pk to find if we have the PKS
disease: multiple subkeys with their binding sigs stripped, and the
sig for the first subkey placed after the last subkey. That is,
instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have
"pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2
and sub3, as they are already lost, but we can try and rescue sub1
by reordering the keyblock so that it reads "pk uid sig sub1 bind1
sub2 sub3". Returns TRUE if the keyblock was modified. */
static int
fix_pks_corruption (ctrl_t ctrl, kbnode_t keyblock)
{
int changed = 0;
int keycount = 0;
kbnode_t node;
kbnode_t last = NULL;
kbnode_t sknode=NULL;
/* First determine if we have the problem at all. Look for 2 or
more subkeys in a row, followed by a single binding sig. */
for (node=keyblock; node; last=node, node=node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keycount++;
if(!sknode)
sknode=node;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& keycount >= 2
&& !node->next)
{
/* We might have the problem, as this key has two subkeys in
a row without any intervening packets. */
/* Sanity check */
if (!last)
break;
/* Temporarily attach node to sknode. */
node->next = sknode->next;
sknode->next = node;
last->next = NULL;
/* Note we aren't checking whether this binding sig is a
selfsig. This is not necessary here as the subkey and
binding sig will be rejected later if that is the
case. */
if (check_key_signature (ctrl, keyblock,node,NULL))
{
/* Not a match, so undo the changes. */
sknode->next = node->next;
last->next = node;
node->next = NULL;
break;
}
else
{
/* Mark it good so we don't need to check it again */
sknode->flag |= NODE_GOOD_SELFSIG;
changed = 1;
break;
}
}
else
keycount = 0;
}
return changed;
}
/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus
direct key signatures. A side effect of this was that a later
import of the same good direct key signatures was not possible
because the cmp_signature check in merge_blocks considered them
equal. Although direct key signatures are now checked during
import, there might still be bogus signatures sitting in a keyring.
We need to detect and delete them before doing a merge. This
function returns the number of removed sigs. */
static int
fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid)
{
gpg_error_t err;
kbnode_t node;
int count = 0;
for (node = keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
err = check_key_signature (ctrl, keyblock, node, NULL);
if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO )
{
/* If we don't know the error, we can't decide; this is
not a problem because cmp_signature can't compare the
signature either. */
log_info ("key %s: invalid direct key signature removed\n",
keystr (keyid));
delete_kbnode (node);
count++;
}
}
}
return count;
}
static void
print_import_ok (PKT_public_key *pk, unsigned int reason)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char buf[MAX_FINGERPRINT_LEN*2+30], *p;
size_t i, n;
snprintf (buf, sizeof buf, "%u ", reason);
p = buf + strlen (buf);
fingerprint_from_pk (pk, array, &n);
s = array;
for (i=0; i < n ; i++, s++, p += 2)
sprintf (p, "%02X", *s);
write_status_text (STATUS_IMPORT_OK, buf);
}
static void
print_import_check (PKT_public_key * pk, PKT_user_id * id)
{
byte hexfpr[2*MAX_FINGERPRINT_LEN+1];
u32 keyid[2];
keyid_from_pk (pk, keyid);
hexfingerprint (pk, hexfpr, sizeof hexfpr);
write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s",
keyid[0], keyid[1], hexfpr, id->name);
}
static void
check_prefs_warning(PKT_public_key *pk)
{
log_info(_("WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"), keystr_from_pk(pk));
}
static void
check_prefs (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int problem=0;
merge_keys_and_selfsig (ctrl, keyblock);
pk=keyblock->pkt->pkt.public_key;
for(node=keyblock;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->created
&& node->pkt->pkt.user_id->prefs)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
char *user = utf8_to_native(uid->name,strlen(uid->name),0);
for(;prefs->type;prefs++)
{
char num[10]; /* prefs->value is a byte, so we're over
safe here */
sprintf(num,"%u",prefs->value);
if(prefs->type==PREFTYPE_SYM)
{
if (openpgp_cipher_test_algo (prefs->value))
{
const char *algo =
(openpgp_cipher_test_algo (prefs->value)
? num
: openpgp_cipher_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for cipher"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_AEAD)
{
if (openpgp_aead_test_algo (prefs->value))
{
/* FIXME: The test below is wrong. We should
* check if ...algo_name yields a "?" and
* only in that case use NUM. */
const char *algo =
(openpgp_aead_test_algo (prefs->value)
? num
: openpgp_aead_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for AEAD"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_HASH)
{
if(openpgp_md_test_algo(prefs->value))
{
const char *algo =
(gcry_md_test_algo (prefs->value)
? num
: gcry_md_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for digest"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_ZIP)
{
if(check_compress_algo (prefs->value))
{
const char *algo=compress_algo_to_string(prefs->value);
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for compression"
" algorithm %s\n"),user,algo?algo:num);
problem=1;
}
}
}
xfree(user);
}
}
if(problem)
{
log_info(_("it is strongly suggested that you update"
" your preferences and\n"));
log_info(_("re-distribute this key to avoid potential algorithm"
" mismatch problems\n"));
if(!opt.batch)
{
strlist_t sl = NULL;
strlist_t locusr = NULL;
size_t fprlen=0;
byte fpr[MAX_FINGERPRINT_LEN], *p;
char username[(MAX_FINGERPRINT_LEN*2)+1];
unsigned int i;
p = fingerprint_from_pk (pk,fpr,&fprlen);
for(i=0;i<fprlen;i++,p++)
sprintf(username+2*i,"%02X",*p);
add_to_strlist(&locusr,username);
append_to_strlist(&sl,"updpref");
append_to_strlist(&sl,"save");
keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
free_strlist(sl);
free_strlist(locusr);
}
else if(!opt.quiet)
log_info(_("you can update your preferences with:"
" gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
}
}
/* Helper for apply_*_filter in import.c and export.c. */
const char *
impex_filter_getval (void *cookie, const char *propname)
{
/* FIXME: Malloc our static buffers and access them via PARM. */
struct impex_filter_parm_s *parm = cookie;
ctrl_t ctrl = parm->ctrl;
kbnode_t node = parm->node;
static char numbuf[20];
const char *result;
log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC);
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!strcmp (propname, "uid"))
result = uid->name;
else if (!strcmp (propname, "mbox"))
{
if (!uid->mbox)
{
uid->mbox = mailbox_from_userid (uid->name, 0);
}
result = uid->mbox;
}
else if (!strcmp (propname, "primary"))
{
result = uid->flags.primary? "1":"0";
}
else if (!strcmp (propname, "expired"))
{
result = uid->flags.expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = uid->flags.revoked? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (!strcmp (propname, "sig_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "sig_created_d"))
{
result = dateonlystr_from_sig (sig);
}
else if (!strcmp (propname, "sig_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "sig_digest_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo);
result = numbuf;
}
else if (!strcmp (propname, "expired"))
{
result = sig->flags.expired? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (!strcmp (propname, "secret"))
{
result = (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0";
}
else if (!strcmp (propname, "key_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "key_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "key_created_d"))
{
result = dateonlystr_from_pk (pk);
}
else if (!strcmp (propname, "expired"))
{
result = pk->has_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = pk->flags.revoked? "1":"0";
}
else if (!strcmp (propname, "disabled"))
{
result = pk_is_disabled (pk)? "1":"0";
}
else if (!strcmp (propname, "usage"))
{
snprintf (numbuf, sizeof numbuf, "%s%s%s%s%s",
(pk->pubkey_usage & PUBKEY_USAGE_ENC)?"e":"",
(pk->pubkey_usage & PUBKEY_USAGE_SIG)?"s":"",
(pk->pubkey_usage & PUBKEY_USAGE_CERT)?"c":"",
(pk->pubkey_usage & PUBKEY_USAGE_AUTH)?"a":"",
(pk->pubkey_usage & PUBKEY_USAGE_UNKNOWN)?"?":"");
result = numbuf;
}
else
result = NULL;
}
else
result = NULL;
return result;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-sig filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
int active = 0;
u32 main_keyid[2];
PKT_signature *sig;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid);
/* Loop over all signatures for user id and attribute packets which
* are not self signatures. */
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready. */
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
active = 1;
if (!active)
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1])
continue; /* Skip self-signatures. */
if (IS_UID_SIG(sig) || IS_UID_REV(sig))
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
delete_kbnode (node);
}
}
}
/* Insert a key origin into a public key packet. */
static gpg_error_t
insert_key_origin_pk (PKT_public_key *pk, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* For WKD and DANE we insert origin information also for the
* key but we don't record the URL because we have have no use
* for that: An update using a keyserver has higher precedence
* and will thus update this origin info. For refresh using WKD
* or DANE we need to go via the User ID anyway. Recall that we
* are only inserting a new key. */
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we add the meta information. Note that the use of a
* fingerprint needs to be enforced by the caller of the import
* function. This is commonly triggered by verifying a modern
* signature which has an Issuer Fingerprint signature
* subpacket. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
else if (origin == KEYORG_FILE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
if (url)
{
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/* Insert a key origin into a user id packet. */
static gpg_error_t
insert_key_origin_uid (PKT_user_id *uid, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We insert origin information on a UID only when we received
* them via the Web Key Directory or a DANE record. The key we
* receive here from the WKD has been filtered to contain only
* the user ID as looked up in the WKD. For a DANE origin we
* this should also be the case. Thus we will see here only one
* user id. */
uid->keyorg = origin;
uid->keyupdate = curtime;
if (url)
{
xfree (uid->updateurl);
uid->updateurl = xtrystrdup (url);
if (!uid->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we mark that also in the user ID. However we do not
* store the keyserver URL in the UID. A later update (merge)
* from a more trusted source will replace this info. */
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_FILE)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
return 0;
}
/* Apply meta data to KEYBLOCK. This sets the origin of the key to
* ORIGIN and the updateurl to URL. Note that this function is only
* used for a new key, that is not when we are merging keys. */
static gpg_error_t
insert_key_origin (kbnode_t keyblock, int origin, const char *url)
{
gpg_error_t err;
kbnode_t node;
u32 curtime = make_timestamp ();
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
err = insert_key_origin_pk (node->pkt->pkt.public_key, curtime,
origin, url);
if (err)
return err;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
err = insert_key_origin_uid (node->pkt->pkt.user_id, curtime,
origin, url);
if (err)
return err;
}
}
return 0;
}
/* Update meta data on KEYBLOCK. This updates the key origin on the
* public key according to ORIGIN and URL. The UIDs are already
* updated when this function is called. */
static gpg_error_t
update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url)
{
PKT_public_key *pk;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = keyblock->pkt->pkt.public_key;
if (pk->keyupdate > curtime)
; /* Don't do it for a time warp. */
else if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We only update the origin info if they either have never been
* set or are the origin was the same as the new one. If this
* is WKD we also update the UID to show from which user id this
* was updated. */
if (!pk->keyorg || pk->keyorg == KEYORG_WKD || pk->keyorg == KEYORG_DANE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (origin == KEYORG_WKD && url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
}
else if (origin == KEYORG_KS)
{
/* All updates from a keyserver are considered to have the
* freshed key. Thus we always set the new key origin. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_FILE)
{
/* Updates from a file are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
}
else if (origin == KEYORG_URL)
{
/* Updates from a URL are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/*
* Try to import one keyblock. Return an error only in serious cases,
* but never for an invalid keyblock. It uses log_error to increase
* the internal errorcount, so that invalid input can be detected by
* programs which called gpg. If SILENT is no messages are printed -
* even most error messages are suppressed. ORIGIN is the origin of
* the key (0 for unknown) and URL the corresponding URL. FROM_SK
* indicates that the key has been made from a secret key. If R_SAVED
* is not NULL a boolean will be stored indicating whether the keyblock
* has valid parts.
*/
static gpg_error_t
import_one_real (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid)
{
gpg_error_t err = 0;
PKT_public_key *pk;
kbnode_t node, uidnode;
kbnode_t keyblock_orig = NULL;
byte fpr2[MAX_FINGERPRINT_LEN];
size_t fpr2len;
u32 keyid[2];
int new_key = 0;
int mod_key = 0;
int same_key = 0;
int non_self = 0;
size_t an;
char pkstrbuf[PUBKEY_STRING_SIZE];
int merge_keys_done = 0;
int any_filter = 0;
KEYDB_HANDLE hd = NULL;
if (r_valid)
*r_valid = 0;
/* If show-only is active we don't won't any extra output. */
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN)))
silent = 1;
/* Get the key and print some info about it. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node )
BUG();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr2, &fpr2len);
for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++)
fpr2[an] = 0;
keyid_from_pk( pk, keyid );
uidnode = find_next_kbnode( keyblock, PKT_USER_ID );
if (opt.verbose && !opt.interactive && !silent && !from_sk)
{
/* Note that we do not print this info in FROM_SK mode
* because import_secret_one already printed that. */
log_info ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk), datestr_from_pk(pk) );
if (uidnode)
print_utf8_buffer (log_get_stream (),
uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len );
log_printf ("\n");
}
/* Unless import-drop-uids has been requested we don't allow import
* of a key without UIDs. */
if (!uidnode && !(options & IMPORT_DROP_UIDS))
{
if (!silent)
log_error( _("key %s: no user ID\n"), keystr_from_pk(pk));
return 0;
}
if (screener && screener (keyblock, screener_arg))
{
log_error (_("key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.interactive && !silent)
{
if (is_status_enabled())
print_import_check (pk, uidnode->pkt->pkt.user_id);
merge_keys_and_selfsig (ctrl, keyblock);
tty_printf ("\n");
show_basic_key_info (ctrl, keyblock, from_sk);
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("import.okay",
"Do you want to import this key? (y/N) "))
return 0;
}
- /* Remove all non-self-sigs if requested. Noe that this is a NOP if
+ /* Remove all non-self-sigs if requested. Note that this is a NOP if
* that option has been globally set but we may also be called
* latter with the already parsed keyblock and a locally changed
* option. This is why we need to remove them here as well. */
if ((options & IMPORT_SELF_SIGS_ONLY))
remove_all_non_self_sigs (&keyblock, keyid);
/* Remove or collapse the user ids. */
if ((options & IMPORT_DROP_UIDS))
remove_all_uids (&keyblock);
else
collapse_uids (&keyblock);
/* Clean the key that we're about to import, to cut down on things
that we have to clean later. This has no practical impact on the
end result, but does result in less logging which might confuse
the user. */
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock,
opt.verbose, (options&IMPORT_MINIMAL), NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
clear_kbnode_flags( keyblock );
if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG)
&& fix_pks_corruption (ctrl, keyblock)
&& opt.verbose)
log_info (_("key %s: PKS subkey corruption repaired\n"),
keystr_from_pk(pk));
if ((options & IMPORT_REPAIR_KEYS))
key_check_all_keysigs (ctrl, 1, keyblock, 0, 0);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
return 0; /* Invalid keyblock - error already printed. */
/* If we allow such a thing, mark unsigned uids as valid */
if (opt.allow_non_selfsigned_uid)
{
for (node=keyblock; node; node = node->next )
if (node->pkt->pkttype == PKT_USER_ID
&& !(node->flag & NODE_GOOD_SELFSIG)
&& !(node->flag & NODE_BAD_SELFSIG) )
{
char *user=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
/* Fake a good signature status for the user id. */
node->flag |= NODE_GOOD_SELFSIG;
log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"),
keystr_from_pk(pk),user);
xfree(user);
}
}
/* Delete invalid parts and without the drop option bail out if
* there are no user ids. */
if (!delete_inv_parts (ctrl, keyblock, keyid, options)
&& !(options & IMPORT_DROP_UIDS) )
{
if (!silent)
{
log_error( _("key %s: no valid user IDs\n"), keystr_from_pk(pk));
if (!opt.quiet )
log_info(_("this may be caused by a missing self-signature\n"));
}
stats->no_user_id++;
return 0;
}
/* Get rid of deleted nodes. */
commit_kbnode (&keyblock);
/* Apply import filter. */
if (import_filter.keep_uid)
{
apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid);
commit_kbnode (&keyblock);
any_filter = 1;
}
if (import_filter.drop_sig)
{
apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig);
commit_kbnode (&keyblock);
any_filter = 1;
}
/* If we ran any filter we need to check that at least one user id
* is left in the keyring. Note that we do not use log_error in
* this case. */
if (any_filter && !any_uid_left (keyblock))
{
if (!opt.quiet )
log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk));
stats->no_user_id++;
return 0;
}
/* The keyblock is valid and ready for real import. */
if (r_valid)
*r_valid = 1;
/* Show the key in the form it is merged or inserted. We skip this
* if "import-export" is also active without --armor or the output
* file has explicily been given. */
if ((options & IMPORT_SHOW)
&& !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile))
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
/* Note that we do not want to show the validity because the key
* has not yet imported. */
list_keyblock_direct (ctrl, keyblock, from_sk, 0,
opt.fingerprint || opt.with_fingerprint, 1);
es_fflush (es_stdout);
}
/* Write the keyblock to the output and do not actually import. */
if ((options & IMPORT_EXPORT))
{
if (!merge_keys_done)
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
}
err = write_keyblock_to_output (keyblock, opt.armor, opt.export_options);
goto leave;
}
if (opt.dry_run || (options & IMPORT_DRY_RUN))
goto leave;
/* Do we have this key already in one of our pubrings ? */
err = get_keyblock_byfprint_fast (ctrl, &keyblock_orig, &hd,
fpr2, fpr2len, 1/*locked*/);
if ((err
&& gpg_err_code (err) != GPG_ERR_NO_PUBKEY
&& gpg_err_code (err) != GPG_ERR_UNUSABLE_PUBKEY)
|| !hd)
{
/* The !hd above is to catch a misbehaving function which
* returns NO_PUBKEY for failing to allocate a handle. */
if (!silent)
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (err));
}
else if (err && (opt.import_options&IMPORT_MERGE_ONLY) )
{
if (opt.verbose && !silent )
log_info( _("key %s: new key - skipped\n"), keystr(keyid));
err = 0;
stats->skipped_new_keys++;
}
else if (err) /* Insert this key. */
{
/* Note: ERR can only be NO_PUBKEY or UNUSABLE_PUBKEY. */
int n_sigs_cleaned, n_uids_cleaned;
err = keydb_locate_writable (hd);
if (err)
{
log_error (_("no writable keyring found: %s\n"), gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (opt.verbose > 1 )
log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) );
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose, (options&IMPORT_MINIMAL),
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = insert_key_origin (keyblock, origin, url);
if (err)
{
log_error ("insert_key_origin failed: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
err = keydb_insert_keyblock (hd, keyblock );
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST))
{
/* This should not be possible since we delete the
ownertrust when a key is deleted, but it can happen if
the keyring and trustdb are out of sync. It can also
be made to happen with the trusted-key command and by
importing and locally exported key. */
clear_ownertrusts (ctrl, pk);
if (non_self)
revalidation_mark (ctrl);
}
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info (_("key %s: public key \"%s\" imported\n"),
keystr(keyid), p);
xfree(p);
}
if (!err && is_status_enabled())
{
char *us = get_long_user_id_string (ctrl, keyid);
write_status_text( STATUS_IMPORTED, us );
xfree(us);
print_import_ok (pk, 1);
}
if (!err)
{
stats->imported++;
new_key = 1;
}
}
else /* Key already exists - merge. */
{
int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned;
u32 curtime = make_timestamp ();
/* Compare the original against the new key; just to be sure nothing
* weird is going on */
if (cmp_public_keys (keyblock_orig->pkt->pkt.public_key, pk))
{
if (!silent)
log_error( _("key %s: doesn't match our copy\n"),keystr(keyid));
goto leave;
}
/* Make sure the original direct key sigs are all sane. */
n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid);
if (n_sigs_cleaned)
commit_kbnode (&keyblock_orig);
/* Try to merge KEYBLOCK into KEYBLOCK_ORIG. */
clear_kbnode_flags( keyblock_orig );
clear_kbnode_flags( keyblock );
n_uids = n_sigs = n_subk = n_uids_cleaned = 0;
err = merge_blocks (ctrl, options, keyblock_orig, keyblock, keyid,
curtime, origin, url,
&n_uids, &n_sigs, &n_subk );
if (err)
goto leave;
/* Clean the final keyblock again if requested. we can't do
* this if only self-signatures are imported; see bug #4628. */
if ((options & IMPORT_CLEAN)
&& !(options & IMPORT_SELF_SIGS_ONLY))
{
merge_keys_and_selfsig (ctrl, keyblock_orig);
clean_all_uids (ctrl, keyblock_orig, opt.verbose,
(options&IMPORT_MINIMAL),
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock_orig, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned)
{
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = update_key_origin (keyblock_orig, curtime, origin, url);
if (err)
{
log_error ("update_key_origin failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
mod_key = 1;
/* KEYBLOCK_ORIG has been updated; write */
err = keydb_update_keyblock (ctrl, hd, keyblock_orig);
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (non_self)
revalidation_mark (ctrl);
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. Print and update stats if we got no error.
* An error here comes from writing the keyblock and thus
* very likely means that no update happened. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
if (n_uids == 1 )
log_info( _("key %s: \"%s\" 1 new user ID\n"),
keystr(keyid),p);
else if (n_uids )
log_info( _("key %s: \"%s\" %d new user IDs\n"),
keystr(keyid),p,n_uids);
if (n_sigs == 1 )
log_info( _("key %s: \"%s\" 1 new signature\n"),
keystr(keyid), p);
else if (n_sigs )
log_info( _("key %s: \"%s\" %d new signatures\n"),
keystr(keyid), p, n_sigs );
if (n_subk == 1 )
log_info( _("key %s: \"%s\" 1 new subkey\n"),
keystr(keyid), p);
else if (n_subk )
log_info( _("key %s: \"%s\" %d new subkeys\n"),
keystr(keyid), p, n_subk );
if (n_sigs_cleaned==1)
log_info(_("key %s: \"%s\" %d signature cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
else if (n_sigs_cleaned)
log_info(_("key %s: \"%s\" %d signatures cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
if (n_uids_cleaned==1)
log_info(_("key %s: \"%s\" %d user ID cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
else if (n_uids_cleaned)
log_info(_("key %s: \"%s\" %d user IDs cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
xfree(p);
}
if (!err)
{
stats->n_uids +=n_uids;
stats->n_sigs +=n_sigs;
stats->n_subk +=n_subk;
stats->n_sigs_cleaned +=n_sigs_cleaned;
stats->n_uids_cleaned +=n_uids_cleaned;
if (is_status_enabled () && !silent)
print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
}
}
else
{
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* FIXME: We do not track the time we last checked a key for
* updates. To do this we would need to rewrite even the
* keys which have no changes. Adding this would be useful
* for the automatic update of expired keys via the WKD in
* case the WKD still carries the expired key. See
* get_best_pubkey_byname. */
same_key = 1;
if (is_status_enabled ())
print_import_ok (pk, 0);
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p);
xfree(p);
}
stats->unchanged++;
}
}
leave:
keydb_release (hd);
if (mod_key || new_key || same_key)
{
/* A little explanation for this: we fill in the fingerprint
when importing keys as it can be useful to know the
fingerprint in certain keyserver-related cases (a keyserver
asked for a particular name, but the key doesn't have that
name). However, in cases where we're importing more than
one key at a time, we cannot know which key to fingerprint.
In these cases, rather than guessing, we do not
fingerprinting at all, and we must hope the user ID on the
keys are useful. Note that we need to do this for new
keys, merged keys and even for unchanged keys. This is
required because for example the --auto-key-locate feature
may import an already imported key and needs to know the
fingerprint of the key in all cases. */
if (fpr)
{
/* Note that we need to compare against 0 here because
COUNT gets only incremented after returning from this
function. */
if (!stats->count)
{
xfree (*fpr);
*fpr = fingerprint_from_pk (pk, NULL, fpr_len);
}
else if (origin != KEYORG_WKD)
{
xfree (*fpr);
*fpr = NULL;
}
}
}
/* Now that the key is definitely incorporated into the keydb, we
need to check if a designated revocation is present or if the
prefs are not rational so we can warn the user. */
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock);
}
release_kbnode( keyblock_orig );
return err;
}
/* Wrapper around import_one_real to retry the import in some cases. */
static gpg_error_t
import_one (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid)
{
gpg_error_t err;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid);
if (gpg_err_code (err) == GPG_ERR_TOO_LARGE
&& gpg_err_source (err) == GPG_ERR_SOURCE_KEYBOX
&& ((options & (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN))
!= (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN)))
{
/* We hit the maximum image length. Ask the wrapper to do
* everything again but this time with some extra options. */
u32 keyid[2];
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
log_info ("key %s: keyblock too large, retrying with self-sigs-only\n",
keystr (keyid));
options |= IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid);
}
return err;
}
/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The
* function prints diagnostics and returns an error code. If BATCH is
* true the secret keys are stored by gpg-agent in the transfer format
* (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED
* is set, only those nodes with flag NODE_TRANSFER_SECKEY are
* processed. */
gpg_error_t
transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force,
int only_marked)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
kbnode_t ctx = NULL;
kbnode_t node;
PKT_public_key *main_pk, *pk;
struct seckey_info *ski;
int nskey;
membuf_t mbuf;
int i, j;
void *format_args[2*PUBKEY_MAX_NSKEY];
gcry_sexp_t skey, prot, tmpsexp;
gcry_sexp_t curve = NULL;
unsigned char *transferkey = NULL;
size_t transferkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
char *cache_nonce = NULL;
int stub_key_skipped = 0;
/* Get the current KEK. */
err = agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
/* Note: We need to use walk_kbnode so that we skip nodes which are
* marked as deleted. */
main_pk = NULL;
while ((node = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY))
continue;
pk = node->pkt->pkt.public_key;
if (!main_pk)
main_pk = pk;
/* Make sure the keyids are available. */
keyid_from_pk (pk, NULL);
if (node->pkt->pkttype == PKT_SECRET_KEY)
{
pk->main_keyid[0] = pk->keyid[0];
pk->main_keyid[1] = pk->keyid[1];
}
else
{
pk->main_keyid[0] = main_pk->keyid[0];
pk->main_keyid[1] = main_pk->keyid[1];
}
ski = pk->seckey_info;
if (!ski)
BUG ();
if (stats)
{
stats->count++;
stats->secret_read++;
}
/* We ignore stub keys. The way we handle them in other parts
of the code is by asking the agent whether any secret key is
available for a given keyblock and then concluding that we
have a secret key; all secret (sub)keys of the keyblock the
agent does not know of are then stub keys. This works also
for card stub keys. The learn command or the card-status
command may be used to check with the agent whether a card
has been inserted and a stub key is in turn generated by the
agent. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
stub_key_skipped = 1;
continue;
}
/* Convert our internal secret key object into an S-expression. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
if (!nskey || nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
log_error ("internal error: %s\n", gpg_strerror (err));
goto leave;
}
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
/* The ECC case. */
char *curvestr = openpgp_oid_to_str (pk->pkey[0]);
if (!curvestr)
err = gpg_error_from_syserror ();
else
{
const char *curvename = openpgp_oid_to_curve (curvestr, 1);
gcry_sexp_release (curve);
err = gcry_sexp_build (&curve, NULL, "(curve %s)",
curvename?curvename:curvestr);
xfree (curvestr);
if (!err)
{
j = 0;
/* Append the public key element Q. */
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + 1;
/* Append the secret key element D. For ECDH we
skip PKEY[2] because this holds the KEK which is
not needed by gpg-agent. */
i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
}
else
{
/* Standard case for the old (non-ECC) algorithms. */
for (i=j=0; i < nskey; i++)
{
if (!pk->pkey[i])
continue; /* Protected keys only have NPKEY+1 elements. */
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
put_membuf_str (&mbuf, ")");
put_membuf (&mbuf, "", 1);
if (err)
xfree (get_membuf (&mbuf, NULL));
else
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&skey, NULL, format, format_args);
xfree (format);
}
if (err)
{
log_error ("error building skey array: %s\n", gpg_strerror (err));
goto leave;
}
if (ski->is_protected)
{
char countbuf[35];
/* FIXME: Support AEAD */
/* Note that the IVLEN may be zero if we are working on a
dummy key. We can't express that in an S-expression and
thus we send dummy data for the IV. */
snprintf (countbuf, sizeof countbuf, "%lu",
(unsigned long)ski->s2k.count);
err = gcry_sexp_build
(&prot, NULL,
" (protection %s %s %b %d %s %b %s)\n",
ski->sha1chk? "sha1":"sum",
openpgp_cipher_algo_name (ski->algo),
ski->ivlen? (int)ski->ivlen:1,
ski->ivlen? ski->iv: (const unsigned char*)"X",
ski->s2k.mode,
openpgp_md_algo_name (ski->s2k.hash_algo),
(int)sizeof (ski->s2k.salt), ski->s2k.salt,
countbuf);
}
else
err = gcry_sexp_build (&prot, NULL, " (protection none)\n");
tmpsexp = NULL;
xfree (transferkey);
transferkey = NULL;
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version %d)\n"
" (algo %s)\n"
" %S%S\n"
" (csum %d)\n"
" %S)\n",
pk->version,
openpgp_pk_algo_name (pk->pubkey_algo),
curve, skey,
(int)(unsigned long)ski->csum, prot);
gcry_sexp_release (skey);
gcry_sexp_release (prot);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen);
gcry_sexp_release (tmpsexp);
if (err)
{
log_error ("error building transfer key: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
wrappedkeylen = transferkeylen + 8;
xfree (wrappedkey);
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
err = gpg_error_from_syserror ();
else
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
transferkey, transferkeylen);
if (err)
goto leave;
xfree (transferkey);
transferkey = NULL;
/* Send the wrapped key to the agent. */
{
char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1);
err = agent_import_key (ctrl, desc, &cache_nonce,
wrappedkey, wrappedkeylen, batch, force,
pk->keyid, pk->main_keyid, pk->pubkey_algo);
xfree (desc);
}
if (!err)
{
if (opt.verbose)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk_with_sub (main_pk, pk));
if (stats)
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
if (opt.verbose)
log_info (_("key %s: secret key already exists\n"),
keystr_from_pk_with_sub (main_pk, pk));
err = 0;
if (stats)
stats->secret_dups++;
}
else
{
log_error (_("key %s: error sending to agent: %s\n"),
keystr_from_pk_with_sub (main_pk, pk),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try the other subkeys. */
}
}
if (!err && stub_key_skipped)
/* We need to notify user how to migrate stub keys. */
err = gpg_error (GPG_ERR_NOT_PROCESSED);
leave:
gcry_sexp_release (curve);
xfree (cache_nonce);
xfree (wrappedkey);
xfree (transferkey);
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
}
/* Walk a secret keyblock and produce a public keyblock out of it.
* Returns a new node or NULL on error. Modifies the tag field of the
* nodes. */
static kbnode_t
sec_to_pub_keyblock (kbnode_t sec_keyblock)
{
kbnode_t pub_keyblock = NULL;
kbnode_t ctx = NULL;
kbnode_t secnode, pubnode;
kbnode_t lastnode = NULL;
unsigned int tag = 0;
/* Set a tag to all nodes. */
for (secnode = sec_keyblock; secnode; secnode = secnode->next)
secnode->tag = ++tag;
/* Copy. */
while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (secnode->pkt->pkttype == PKT_SECRET_KEY
|| secnode->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* Make a public key. */
PACKET *pkt;
PKT_public_key *pk;
pkt = xtrycalloc (1, sizeof *pkt);
pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL;
if (!pk)
{
xfree (pkt);
release_kbnode (pub_keyblock);
return NULL;
}
if (secnode->pkt->pkttype == PKT_SECRET_KEY)
pkt->pkttype = PKT_PUBLIC_KEY;
else
pkt->pkttype = PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
pubnode = new_kbnode (pkt);
}
else
{
pubnode = clone_kbnode (secnode);
}
pubnode->tag = secnode->tag;
if (!pub_keyblock)
pub_keyblock = lastnode = pubnode;
else
{
lastnode->next = pubnode;
lastnode = pubnode;
}
}
return pub_keyblock;
}
/* Delete all notes in the keyblock at R_KEYBLOCK which are not in
* PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */
static gpg_error_t
resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock,
kbnode_t *r_removedsecs)
{
kbnode_t sec_keyblock = *r_keyblock;
kbnode_t node, prevnode;
unsigned int *taglist;
unsigned int ntaglist, n;
kbnode_t attic = NULL;
kbnode_t *attic_head = &attic;
/* Collect all tags in an array for faster searching. */
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
ntaglist++;
taglist = xtrycalloc (ntaglist, sizeof *taglist);
if (!taglist)
return gpg_error_from_syserror ();
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
taglist[ntaglist++] = node->tag;
/* Walks over the secret keyblock and delete all nodes which are not
* in the tag list. Those nodes have been deleted in the
* pub_keyblock. Sequential search is a bit lazy and could be
* optimized by sorting and bsearch; however secret keyrings are
* short and there are easier ways to DoS the import. */
again:
for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next)
{
for (n=0; n < ntaglist; n++)
if (taglist[n] == node->tag)
break;
if (n == ntaglist) /* Not in public keyblock. */
{
if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (!prevnode)
sec_keyblock = node->next;
else
prevnode->next = node->next;
node->next = NULL;
*attic_head = node;
attic_head = &node->next;
goto again; /* That's lame; I know. */
}
else
delete_kbnode (node);
}
}
xfree (taglist);
/* Commit the as deleted marked nodes and return the possibly
* modified keyblock and a list of removed secret key nodes. */
commit_kbnode (&sec_keyblock);
*r_keyblock = sec_keyblock;
*r_removedsecs = attic;
return 0;
}
/* Helper for import_secret_one. */
static gpg_error_t
do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
struct import_stats_s *stats, int batch, int only_marked)
{
gpg_error_t err;
struct import_stats_s subkey_stats = {0};
err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
batch, 0, only_marked);
if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
{
/* TRANSLATORS: For a smartcard, each private key on host has a
* reference (stub) to a smartcard and actual private key data
* is stored on the card. A single smartcard can have up to
* three private key data. Importing private key stub is always
* skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED.
* Instead, user should be suggested to run 'gpg --card-status',
* then, references to a card will be automatically created
* again. */
log_info (_("To migrate '%s', with each smartcard, "
"run: %s\n"), "secring.gpg", "gpg --card-status");
err = 0;
}
if (!err)
{
int status = 16;
if (!opt.quiet)
log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk));
if (subkey_stats.secret_imported)
{
status |= 1;
stats->secret_imported += 1;
}
if (subkey_stats.secret_dups)
stats->secret_dups += 1;
if (is_status_enabled ())
print_import_ok (pk, status);
}
return err;
}
/* If the secret keys (main or subkey) in SECKEYS have a corresponding
* public key in the public key described by (FPR,FPRLEN) import these
* parts.
*/
static gpg_error_t
import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch)
{
gpg_error_t err;
kbnode_t pub_keyblock = NULL;
kbnode_t node;
struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL;
size_t n, nfprlist;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
PKT_public_key *pk;
/* Get the entire public key block from our keystore and put all its
* fingerprints into an array. */
err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen);
if (err)
goto leave;
log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
for (nfprlist = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
nfprlist++;
log_assert (nfprlist);
fprlist = xtrycalloc (nfprlist, sizeof *fprlist);
if (!fprlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (n = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
fingerprint_from_pk (node->pkt->pkt.public_key,
fprlist[n].fpr, &fprlist[n].fprlen);
n++;
}
log_assert (n == nfprlist);
/* for (n=0; n < nfprlist; n++) */
/* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */
/* Mark all secret keys which have a matching public key part in
* PUB_KEYBLOCK. */
for (node = seckeys; node; node = node->next)
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue; /* Should not happen. */
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
node->flag &= ~NODE_TRANSFER_SECKEY;
for (n=0; n < nfprlist; n++)
if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen))
{
node->flag |= NODE_TRANSFER_SECKEY;
/* log_debug ("found matching seckey\n"); */
break;
}
}
/* Transfer all marked keys. */
err = do_transfer (ctrl, seckeys, pk, stats, batch, 1);
leave:
xfree (fprlist);
release_kbnode (pub_keyblock);
return err;
}
/* Import function for a single secret keyblock. Handling is simpler
* than for public keys. We allow secret key importing only when
* allow is true, this is so that a secret key can not be imported
* accidentally and thereby tampering with the trust calculation.
*
* Ownership of KEYBLOCK is transferred to this function!
*
* If R_SECATTIC is not null the last special sec_keyblock is stored
* there.
*/
static gpg_error_t
import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic)
{
PKT_public_key *pk;
struct seckey_info *ski;
kbnode_t node, uidnode;
u32 keyid[2];
gpg_error_t err = 0;
int nr_prev;
kbnode_t pub_keyblock;
kbnode_t attic = NULL;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Get the key and print some info about it */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, keyid);
uidnode = find_next_kbnode (keyblock, PKT_USER_ID);
if (screener && screener (keyblock, screener_arg))
{
log_error (_("secret key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
release_kbnode (keyblock);
return 0;
}
if (opt.verbose && !for_migration)
{
log_info ("sec %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk), datestr_from_pk (pk));
if (uidnode)
print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len);
log_printf ("\n");
}
stats->secret_read++;
if ((options & IMPORT_NO_SECKEY))
{
if (!for_migration)
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
if (!uidnode)
{
if (!for_migration)
log_error( _("key %s: no user ID\n"), keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
ski = pk->seckey_info;
if (!ski)
{
/* Actually an internal error. */
log_error ("key %s: secret key info missing\n", keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
/* A quick check to not import keys with an invalid protection
cipher algorithm (only checks the primary key, though). */
if (ski->algo > 110)
{
if (!for_migration)
log_error (_("key %s: secret key with invalid cipher %d"
" - skipped\n"), keystr_from_pk (pk), ski->algo);
release_kbnode (keyblock);
return 0;
}
#ifdef ENABLE_SELINUX_HACKS
if (1)
{
/* We don't allow importing secret keys because that may be used
to put a secret key into the keyring and the user might later
be tricked into signing stuff with that key. */
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
#endif
clear_kbnode_flags (keyblock);
nr_prev = stats->skipped_new_keys;
/* Make a public key out of the key. */
pub_keyblock = sec_to_pub_keyblock (keyblock);
if (!pub_keyblock)
{
err = gpg_error_from_syserror ();
log_error ("key %s: failed to create public key from secret key\n",
keystr_from_pk (pk));
}
else
{
int valid;
/* Note that this outputs an IMPORT_OK status message for the
public key block, and below we will output another one for
the secret keys. FIXME? */
import_one (ctrl, pub_keyblock, stats,
NULL, NULL, options, 1, for_migration,
screener, screener_arg, 0, NULL, &valid);
/* The secret keyblock may not have nodes which are deleted in
* the public keyblock. Otherwise we would import just the
* secret key without having the public key. That would be
* surprising and clutters our private-keys-v1.d. */
err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic);
if (err)
goto leave;
if (!valid)
{
/* If the block was not valid the primary key is left in the
* original keyblock because we require that for the first
* node. Move it to ATTIC. */
if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
node = keyblock;
keyblock = node->next;
node->next = NULL;
if (attic)
{
node->next = attic;
attic = node;
}
else
attic = node;
}
/* Try to import the secret key iff we have a public key. */
if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
else
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* log_debug ("attic is:\n"); */
/* dump_kbnode (attic); */
/* Proceed with the valid parts of PUBKEYBLOCK. */
/* At least we cancel the secret key import when the public key
import was skipped due to MERGE_ONLY option and a new
key. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN))
&& stats->skipped_new_keys <= nr_prev)
{
/* Read the keyblock again to get the effects of a merge for
* the public key. */
err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen);
if (err || !node)
log_error ("key %s: failed to re-lookup public key: %s\n",
keystr_from_pk (pk), gpg_strerror (err));
else
{
err = do_transfer (ctrl, keyblock, pk, stats, batch, 0);
if (!err)
check_prefs (ctrl, node);
release_kbnode (node);
if (!err && attic)
{
/* Try to import invalid subkeys. This can be the
* case if the primary secret key was imported due
* to --allow-non-selfsigned-uid. */
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
}
}
}
}
leave:
release_kbnode (keyblock);
release_kbnode (pub_keyblock);
if (r_secattic)
*r_secattic = attic;
else
release_kbnode (attic);
return err;
}
/* Return the recocation reason from signature SIG. If no revocation
- * reason is availabale 0 is returned, in other cases the reason
+ * reason is available 0 is returned, in other cases the reason
* (0..255). If R_REASON is not NULL a malloced textual
* representation of the code is stored there. If R_COMMENT is not
* NULL the comment from the reason is stored there and its length at
* R_COMMENTLEN. Note that the value at R_COMMENT is not filtered but
* user supplied data in UTF8; thus it needs to be escaped for display
* purposes. Both return values are either NULL or a malloced
* string/buffer. */
int
get_revocation_reason (PKT_signature *sig, char **r_reason,
char **r_comment, size_t *r_commentlen)
{
int reason_seq = 0;
size_t reason_n;
const byte *reason_p;
char reason_code_buf[20];
const char *reason_text = NULL;
int reason_code = 0;
if (r_reason)
*r_reason = NULL;
if (r_comment)
*r_comment = NULL;
/* Skip over empty reason packets. */
while ((reason_p = enum_sig_subpkt (sig, 1, SIGSUBPKT_REVOC_REASON,
&reason_n, &reason_seq, NULL))
&& !reason_n)
;
if (reason_p)
{
reason_code = *reason_p;
reason_n--; reason_p++;
switch (reason_code)
{
case 0x00: reason_text = _("No reason specified"); break;
case 0x01: reason_text = _("Key is superseded"); break;
case 0x02: reason_text = _("Key has been compromised"); break;
case 0x03: reason_text = _("Key is no longer used"); break;
case 0x20: reason_text = _("User ID is no longer valid"); break;
default:
snprintf (reason_code_buf, sizeof reason_code_buf,
"code=%02x", reason_code);
reason_text = reason_code_buf;
break;
}
if (r_reason)
*r_reason = xstrdup (reason_text);
if (r_comment && reason_n)
{
*r_comment = xmalloc (reason_n);
memcpy (*r_comment, reason_p, reason_n);
*r_commentlen = reason_n;
}
}
return reason_code;
}
/* List the recocation signature as a "rvs" record. SIGRC shows the
* character from the signature verification or 0 if no public key was
* found. */
static void
list_standalone_revocation (ctrl_t ctrl, PKT_signature *sig, int sigrc)
{
char *siguid = NULL;
size_t siguidlen = 0;
char *issuer_fpr = NULL;
int reason_code = 0;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
{
int nouid;
siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid);
if (nouid)
sigrc = '?';
}
reason_code = get_revocation_reason (sig, &reason_text,
&reason_comment, &reason_commentlen);
if (opt.with_colons)
{
es_fputs ("rvs:", es_stdout);
if (sigrc)
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:::",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (reason_text)
es_fprintf (es_stdout, ",%02x", reason_code);
es_fputs ("::", es_stdout);
if ((issuer_fpr = issuer_fpr_string (sig)))
es_fputs (issuer_fpr, es_stdout);
es_fprintf (es_stdout, ":::%d:", sig->digest_algo);
if (reason_comment)
{
es_fputs ("::::", es_stdout);
es_write_sanitized (es_stdout, reason_comment, reason_commentlen,
":", NULL);
es_putc (':', es_stdout);
}
es_putc ('\n', es_stdout);
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
else /* Human readable. */
{
es_fputs ("rvs", es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (siguid)
{
es_fprintf (es_stdout, " ");
print_utf8_buffer (es_stdout, siguid, siguidlen);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
if (reason_text)
{
es_fprintf (es_stdout, " %s%s\n",
_("reason for revocation: "), reason_text);
if (reason_comment)
{
const byte *s, *s_lf;
size_t n, n_lf;
s = reason_comment;
n = reason_commentlen;
s_lf = NULL;
do
{
/* We don't want any empty lines, so we skip them. */
for (;n && *s == '\n'; s++, n--)
;
if (n)
{
s_lf = memchr (s, '\n', n);
n_lf = s_lf? s_lf - s : n;
es_fprintf (es_stdout, " %s",
_("revocation comment: "));
es_write_sanitized (es_stdout, s, n_lf, NULL, NULL);
es_putc ('\n', es_stdout);
s += n_lf; n -= n_lf;
}
} while (s_lf);
}
}
}
es_fflush (es_stdout);
xfree (reason_text);
xfree (reason_comment);
xfree (siguid);
xfree (issuer_fpr);
}
/****************
* Import a revocation certificate; this is a single signature packet.
*/
static int
import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats)
{
PKT_public_key *pk = NULL;
kbnode_t onode;
kbnode_t keyblock = NULL;
KEYDB_HANDLE hd = NULL;
u32 keyid[2];
int rc = 0;
int sigrc = 0;
int silent;
/* No error output for --show-keys. */
silent = (options & (IMPORT_SHOW | IMPORT_DRY_RUN));
log_assert (!node->next );
log_assert (node->pkt->pkttype == PKT_SIGNATURE );
log_assert (IS_KEY_REV (node->pkt->pkt.signature));
keyid[0] = node->pkt->pkt.signature->keyid[0];
keyid[1] = node->pkt->pkt.signature->keyid[1];
pk = xmalloc_clear( sizeof *pk );
rc = get_pubkey (ctrl, pk, keyid );
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY )
{
if (!silent)
log_error (_("key %s: no public key -"
" can't apply revocation certificate\n"), keystr(keyid));
rc = 0;
goto leave;
}
else if (rc )
{
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* Read the original keyblock. */
hd = keydb_new (ctrl);
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
fingerprint_from_pk (pk, afp, &an);
rc = keydb_search_fpr (hd, afp, an);
}
if (rc)
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock );
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* it is okay, that node is not in keyblock because
* check_key_signature works fine for sig_class 0x20 (KEY_REV) in
* this special case. SIGRC is only used for IMPORT_SHOW. */
rc = check_key_signature (ctrl, keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY: sigrc = '?'; break;
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
if (rc )
{
if (!silent)
log_error (_("key %s: invalid revocation certificate"
": %s - rejected\n"), keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* check whether we already have this */
for(onode=keyblock->next; onode; onode=onode->next ) {
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& !cmp_signatures(node->pkt->pkt.signature,
onode->pkt->pkt.signature))
{
rc = 0;
goto leave; /* yes, we already know about it */
}
}
/* insert it */
insert_kbnode( keyblock, clone_kbnode(node), 0 );
/* and write the keyblock back unless in dry run mode. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
rc = keydb_update_keyblock (ctrl, hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
keydb_release (hd);
hd = NULL;
/* we are ready */
if (!opt.quiet )
{
char *p=get_user_id_native (ctrl, keyid);
log_info( _("key %s: \"%s\" revocation certificate imported\n"),
keystr(keyid),p);
xfree(p);
}
/* If the key we just revoked was ultimately trusted, remove its
* ultimate trust. This doesn't stop the user from putting the
* ultimate trust back, but is a reasonable solution for now. */
if (get_ownertrust (ctrl, pk) == TRUST_ULTIMATE)
clear_ownertrusts (ctrl, pk);
revalidation_mark (ctrl);
}
stats->n_revoc++;
leave:
if ((options & IMPORT_SHOW))
list_standalone_revocation (ctrl, node->pkt->pkt.signature, sigrc);
keydb_release (hd);
release_kbnode( keyblock );
free_public_key( pk );
return rc;
}
/* Loop over the KEYBLOCK and check all self signatures. KEYID is the
* keyid of the primary key for reporting purposes. On return the
* following bits in the node flags are set:
*
* - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature
* - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature
* - NODE_DELETION_MARK :: This node shall be deleted
*
* NON_SELF is set to true if there are any sigs other than self-sigs
* in this keyblock.
*
* Returns 0 on success or -1 (but not an error code) if the keyblock
* is invalid.
*/
static int
chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self)
{
kbnode_t knode = NULL; /* The node of the current subkey. */
PKT_public_key *subpk = NULL; /* and its packet. */
kbnode_t bsnode = NULL; /* Subkey binding signature node. */
u32 bsdate = 0; /* Timestamp of that node. */
kbnode_t rsnode = NULL; /* Subkey recocation signature node. */
u32 rsdate = 0; /* Timestamp of that node. */
PKT_signature *sig;
int rc;
kbnode_t n;
for (n=keyblock; (n = find_next_kbnode (n, 0)); )
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
knode = n;
subpk = knode->pkt->pkt.public_key;
bsdate = 0;
rsdate = 0;
bsnode = NULL;
rsnode = NULL;
continue;
}
if ( n->pkt->pkttype != PKT_SIGNATURE )
continue;
sig = n->pkt->pkt.signature;
if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] )
{
*non_self = 1;
continue;
}
/* This just caches the sigs for later use. That way we
import a fully-cached key which speeds things up. */
if (!opt.no_sig_cache)
check_key_signature (ctrl, keyblock, n, NULL);
if ( IS_UID_SIG(sig) || IS_UID_REV(sig) )
{
kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID );
if ( !unode )
{
log_error( _("key %s: no user ID for signature\n"),
keystr(keyid));
return -1; /* The complete keyblock is invalid. */
}
/* If it hasn't been marked valid yet, keep trying. */
if (!(unode->flag & NODE_GOOD_SELFSIG))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if ( opt.verbose )
{
char *p = utf8_to_native
(unode->pkt->pkt.user_id->name,
strlen (unode->pkt->pkt.user_id->name),0);
log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key "
"algorithm on user ID \"%s\"\n"):
_("key %s: invalid self-signature "
"on user ID \"%s\"\n"),
keystr (keyid),p);
xfree (p);
}
}
else
unode->flag |= NODE_GOOD_SELFSIG;
}
}
else if (IS_KEY_SIG (sig))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key algorithm\n"):
_("key %s: invalid direct key signature\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
}
else if ( IS_SUBKEY_SIG (sig) )
{
/* Note that this works based solely on the timestamps like
the rest of gpg. If the standard gets revocation
targets, this may need to be revised. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key"
" algorithm\n"):
_("key %s: invalid subkey binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= bsdate)
{
knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */
if (bsnode)
{
/* Delete the last binding sig since this
one is newer */
bsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (_("key %s: removed multiple subkey"
" binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
}
bsnode = n;
bsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
else if ( IS_SUBKEY_REV (sig) )
{
/* We don't actually mark the subkey as revoked right now,
so just check that the revocation sig is the most recent
valid one. Note that we don't care if the binding sig is
newer than the revocation sig. See the comment in
getkey.c:merge_selfsigs_subkey for more. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if(opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public"
" key algorithm\n"):
_("key %s: invalid subkey revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= rsdate)
{
if (rsnode)
{
/* Delete the last revocation sig since
this one is newer. */
rsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" revocation\n"),keystr(keyid));
}
rsnode = n;
rsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
}
return 0;
}
/* Delete all parts which are invalid and those signatures whose
* public key algorithm is not available in this implementation; but
* consider RSA as valid, because parse/build_packets knows about it.
*
* Returns: True if at least one valid user-id is left over.
*/
static int
delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
unsigned int options)
{
kbnode_t node;
int nvalid=0, uid_seen=0, subkey_seen=0;
PKT_public_key *pk;
for (node=keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid_seen = 1;
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
char *p=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
log_info( _("key %s: skipped user ID \"%s\"\n"),
keystr(keyid),p);
xfree(p);
}
delete_kbnode( node ); /* the user-id */
/* and all following packets up to the next user-id */
while (node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){
delete_kbnode( node->next );
node = node->next;
}
}
else
nvalid++;
}
else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, NULL);
log_info (_("key %s: skipped subkey\n"),
keystr_with_sub (keyid, pk->keyid));
}
delete_kbnode( node ); /* the subkey */
/* and all following signature packets */
while (node->next
&& node->next->pkt->pkttype == PKT_SIGNATURE ) {
delete_kbnode( node->next );
node = node->next;
}
}
else
subkey_seen = 1;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo)
&& node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA )
{
delete_kbnode( node ); /* build_packet() can't handle this */
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
&& !have_secret_key_with_kid (ctrl,
node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
* the secret key used to create this signature - it
* seems that this makes sense */
if(opt.verbose)
log_info( _("key %s: non exportable signature"
" (class 0x%02X) - skipped\n"),
keystr(keyid), node->pkt->pkt.signature->sig_class );
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
if (uid_seen )
{
if(opt.verbose)
log_info( _("key %s: revocation certificate"
" at wrong place - skipped\n"),keystr(keyid));
delete_kbnode( node );
}
else
{
/* If the revocation cert is from a different key than
the one we're working on don't check it - it's
probably from a revocation key and won't be
verifiable with this key anyway. */
if(node->pkt->pkt.signature->keyid[0]==keyid[0]
&& node->pkt->pkt.signature->keyid[1]==keyid[1])
{
int rc = check_key_signature (ctrl, keyblock, node, NULL);
if (rc )
{
if(opt.verbose)
log_info( _("key %s: invalid revocation"
" certificate: %s - skipped\n"),
keystr(keyid), gpg_strerror (rc));
delete_kbnode( node );
}
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature))
&& !subkey_seen )
{
if(opt.verbose)
log_info( _("key %s: subkey signature"
" in wrong place - skipped\n"), keystr(keyid));
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !IS_CERT(node->pkt->pkt.signature))
{
if(opt.verbose)
log_info(_("key %s: unexpected signature class (0x%02X) -"
" skipped\n"),keystr(keyid),
node->pkt->pkt.signature->sig_class);
delete_kbnode(node);
}
else if ((node->flag & NODE_DELETION_MARK))
delete_kbnode( node );
}
/* note: because keyblock is the public key, it is never marked
* for deletion and so keyblock cannot change */
commit_kbnode( &keyblock );
return nvalid;
}
/* This function returns true if any UID is left in the keyring. */
static int
any_uid_left (kbnode_t keyblock)
{
kbnode_t node;
for (node=keyblock->next; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
return 1;
return 0;
}
/* Delete all user ids from KEYBLOCK.
* Returns: True if the keyblock has changed. */
static int
remove_all_uids (kbnode_t *keyblock)
{
kbnode_t node;
int any = 0;
for (node = *keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype != PKT_USER_ID)
continue;
/* We are at the first user id. Delete everything up to the
* first subkey. */
for (; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
delete_kbnode (node);
any = 1;
}
break; /* All done. */
}
commit_kbnode (keyblock);
return any;
}
/* Delete all non-self-sigs from KEYBLOCK.
* Returns: True if the keyblock has changed. */
static void
remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid)
{
kbnode_t node;
unsigned int dropped = 0;
for (node = *keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
if (node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1])
continue;
delete_kbnode (node);
dropped++;
}
if (dropped)
commit_kbnode (keyblock);
if (dropped && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped);
}
/*
* It may happen that the imported keyblock has duplicated user IDs.
* We check this here and collapse those user IDs together with their
* sigs into one.
* Returns: True if the keyblock has changed.
*/
int
collapse_uids (kbnode_t *keyblock)
{
kbnode_t uid1;
int any=0;
for(uid1=*keyblock;uid1;uid1=uid1->next)
{
kbnode_t uid2;
if(is_deleted_kbnode(uid1))
continue;
if(uid1->pkt->pkttype!=PKT_USER_ID)
continue;
for(uid2=uid1->next;uid2;uid2=uid2->next)
{
if(is_deleted_kbnode(uid2))
continue;
if(uid2->pkt->pkttype!=PKT_USER_ID)
continue;
if(cmp_user_ids(uid1->pkt->pkt.user_id,
uid2->pkt->pkt.user_id)==0)
{
/* We have a duplicated uid */
kbnode_t sig1,last;
any=1;
/* Now take uid2's signatures, and attach them to
uid1 */
for(last=uid2;last->next;last=last->next)
{
if(is_deleted_kbnode(last))
continue;
if(last->next->pkt->pkttype==PKT_USER_ID
|| last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| last->next->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
}
/* Snip out uid2 */
(find_prev_kbnode(*keyblock,uid2,0))->next=last->next;
/* Now put uid2 in place as part of uid1 */
last->next=uid1->next;
uid1->next=uid2;
delete_kbnode(uid2);
/* Now dedupe uid1 */
for(sig1=uid1->next;sig1;sig1=sig1->next)
{
kbnode_t sig2;
if(is_deleted_kbnode(sig1))
continue;
if(sig1->pkt->pkttype==PKT_USER_ID
|| sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig1->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig1->pkt->pkttype!=PKT_SIGNATURE)
continue;
for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next)
{
if(is_deleted_kbnode(sig2))
continue;
if(sig2->pkt->pkttype==PKT_USER_ID
|| sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig2->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig2->pkt->pkttype!=PKT_SIGNATURE)
continue;
if(cmp_signatures(sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature)==0)
{
/* We have a match, so delete the second
signature */
delete_kbnode(sig2);
sig2=last;
}
}
}
}
}
}
commit_kbnode(keyblock);
if(any && !opt.quiet)
{
const char *key="???";
if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
log_info (_("key %s: duplicated user ID detected - merged\n"), key);
}
return any;
}
/* Check for a 0x20 revocation from a revocation key that is not
present. This may be called without the benefit of merge_xxxx so
you can't rely on pk->revkey and friends. */
static void
revocation_present (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t onode, inode;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
for(onode=keyblock->next;onode;onode=onode->next)
{
/* If we reach user IDs, we're done. */
if(onode->pkt->pkttype==PKT_USER_ID)
break;
if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& onode->pkt->pkt.signature->revkey)
{
int idx;
PKT_signature *sig=onode->pkt->pkt.signature;
for(idx=0;idx<sig->numrevkeys;idx++)
{
u32 keyid[2];
keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr,
sig->revkey[idx].fprlen, keyid);
for(inode=keyblock->next;inode;inode=inode->next)
{
/* If we reach user IDs, we're done. */
if(inode->pkt->pkttype==PKT_USER_ID)
break;
if (inode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (inode->pkt->pkt.signature)
&& inode->pkt->pkt.signature->keyid[0]==keyid[0]
&& inode->pkt->pkt.signature->keyid[1]==keyid[1])
{
/* Okay, we have a revocation key, and a
* revocation issued by it. Do we have the key
* itself? */
gpg_error_t err;
err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
{
char *tempkeystr = xstrdup (keystr_from_pk (pk));
/* No, so try and get it */
if ((opt.keyserver_options.options
& KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (ctrl))
{
log_info(_("WARNING: key %s may be revoked:"
" fetching revocation key %s\n"),
tempkeystr,keystr(keyid));
keyserver_import_fprint (ctrl,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen,
opt.keyserver, 0);
/* Do we have it now? */
err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
}
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
log_info(_("WARNING: key %s may be revoked:"
" revocation key %s not present.\n"),
tempkeystr,keystr(keyid));
xfree(tempkeystr);
}
}
}
}
}
}
}
/*
* compare and merge the blocks
*
* o compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* o Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* Note: We indicate newly inserted packets with NODE_FLAG_A.
*/
static int
merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig, kbnode_t keyblock,
u32 *keyid, u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk )
{
kbnode_t onode, node;
int rc, found;
/* 1st: handle revocation certificates */
for (node=keyblock->next; node; node=node->next )
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found)
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
{
char *p = get_user_id_native (ctrl, keyid);
log_info(_("key %s: \"%s\" revocation"
" certificate added\n"), keystr(keyid),p);
xfree(p);
}
}
}
}
/* 2nd: merge in any direct key (0x1F) sigs */
for(node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID)
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found )
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
log_info( _("key %s: direct key signature added\n"),
keystr(keyid));
}
}
}
/* 3rd: try to merge new certificates in */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID)
{
/* find the user id in the imported keyblock */
for (node=keyblock->next; node; node=node->next)
if (node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (node ) /* found: merge */
{
rc = merge_sigs (onode, node, n_sigs);
if (rc )
return rc;
}
}
}
/* 4th: add new user-ids */
for (node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
/* do we have this in the original keyblock */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (!onode ) /* this is a new user id: append */
{
rc = append_new_uid (options, keyblock_orig, node,
curtime, origin, url, n_sigs);
if (rc )
return rc;
++*n_uids;
}
}
}
/* 5th: add new subkeys */
for (node=keyblock->next; node; node=node->next)
{
onode = NULL;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
/* do we have this in the original keyblock? */
for(onode=keyblock_orig->next; onode; onode=onode->next)
if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key))
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc)
return rc;
++*n_subk;
}
}
else if (node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* do we have this in the original keyblock? */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_SECRET_SUBKEY
&& !cmp_public_keys (onode->pkt->pkt.public_key,
node->pkt->pkt.public_key) )
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_subk;
}
}
}
/* 6th: merge subkey certificates */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A)
&& (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| onode->pkt->pkttype == PKT_SECRET_SUBKEY))
{
/* find the subkey in the imported keyblock */
for(node=keyblock->next; node; node=node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key ) )
break;
}
if (node) /* Found: merge. */
{
rc = merge_keysigs( onode, node, n_sigs);
if (rc )
return rc;
}
}
}
return 0;
}
/* Helper function for merge_blocks.
*
* Append the new userid starting with NODE and all signatures to
* KEYBLOCK. ORIGIN and URL conveys the usual key origin info. The
* integer at N_SIGS is updated with the number of new signatures.
*/
static gpg_error_t
append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node, u32 curtime,
int origin, const char *url, int *n_sigs)
{
gpg_error_t err;
kbnode_t n;
kbnode_t n_where = NULL;
log_assert (node->pkt->pkttype == PKT_USER_ID);
/* Find the right position for the new user id and its signatures. */
for (n = keyblock; n; n_where = n, n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY )
break;
}
if (!n)
n_where = NULL;
/* and append/insert */
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first. */
n = clone_kbnode(node);
if (n->pkt->pkttype == PKT_USER_ID
&& !(options & IMPORT_RESTORE) )
{
err = insert_key_origin_uid (n->pkt->pkt.user_id,
curtime, origin, url);
if (err)
return err;
}
if (n_where)
{
insert_kbnode( n_where, n, 0 );
n_where = n;
}
else
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID.
* (how should we handle comment packets here?)
*/
static int
merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_USER_ID);
log_assert (src->pkt->pkttype == PKT_USER_ID);
for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next)
{
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
if (IS_SUBKEY_SIG (n->pkt->pkt.signature)
|| IS_SUBKEY_REV (n->pkt->pkt.signature) )
continue; /* skip signatures which are only valid on subkeys */
found = 0;
for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next)
if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature))
{
found++;
break;
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY.
*/
static int
merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| dst->pkt->pkttype == PKT_SECRET_SUBKEY);
for (n=src->next; n ; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
found = 0;
for (n2=dst->next; n2; n2 = n2->next)
{
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n2->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n2->pkt->pkttype == PKT_SIGNATURE
&& (n->pkt->pkt.signature->keyid[0]
== n2->pkt->pkt.signature->keyid[0])
&& (n->pkt->pkt.signature->keyid[1]
== n2->pkt->pkt.signature->keyid[1])
&& (n->pkt->pkt.signature->timestamp
<= n2->pkt->pkt.signature->timestamp)
&& (n->pkt->pkt.signature->sig_class
== n2->pkt->pkt.signature->sig_class))
{
found++;
break;
}
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the subkey starting with NODE and all signatures to KEYBLOCK.
* Mark all new and copied packets by setting flag bit 0.
*/
static int
append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
diff --git a/g10/keydb.c b/g10/keydb.c
index 0068d6829..15291f307 100644
--- a/g10/keydb.c
+++ b/g10/keydb.c
@@ -1,1954 +1,1954 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001-2013 Free Software Foundation, Inc.
* Copyright (C) 2001-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/util.h"
#include "options.h"
#include "main.h" /*try_make_homedir ()*/
#include "packet.h"
#include "keyring.h"
#include "../kbx/keybox.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "keydb-private.h" /* For struct keydb_handle_s */
static int active_handles;
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* A pointer used to check for the primary key database by comparing
to the struct resource_item's TOKEN. */
static void *primary_keydb;
/* Whether we have successfully registered any resource. */
static int any_registered;
/* Looking up keys is expensive. To hide the cost, we cache whether
keys exist in the key database. Then, if we know a key does not
exist, we don't have to spend time looking it up. This
particularly helps the --list-sigs and --check-sigs commands.
The cache stores the results in a hash using separate chaining.
Concretely: we use the LSB of the keyid to index the hash table and
each bucket consists of a linked list of entries. An entry
consists of the 64-bit key id. If a key id is not in the cache,
then we don't know whether it is in the DB or not.
To simplify the cache consistency protocol, we simply flush the
whole cache whenever a key is inserted or updated. */
#define KID_NOT_FOUND_CACHE_BUCKETS 256
static struct kid_not_found_cache_bucket *
kid_not_found_cache[KID_NOT_FOUND_CACHE_BUCKETS];
struct kid_not_found_cache_bucket
{
struct kid_not_found_cache_bucket *next;
u32 kid[2];
};
struct
{
unsigned int count; /* The current number of entries in the hash table. */
unsigned int peak; /* The peak of COUNT. */
unsigned int flushes; /* The number of flushes. */
} kid_not_found_stats;
struct
{
unsigned int handles; /* Number of handles created. */
unsigned int locks; /* Number of locks taken. */
unsigned int parse_keyblocks; /* Number of parse_keyblock_image calls. */
unsigned int get_keyblocks; /* Number of keydb_get_keyblock calls. */
unsigned int build_keyblocks; /* Number of build_keyblock_image calls. */
unsigned int update_keyblocks;/* Number of update_keyblock calls. */
unsigned int insert_keyblocks;/* Number of update_keyblock calls. */
unsigned int delete_keyblocks;/* Number of delete_keyblock calls. */
unsigned int search_resets; /* Number of keydb_search_reset calls. */
unsigned int found; /* Number of successful keydb_search calls. */
unsigned int found_cached; /* Ditto but from the cache. */
unsigned int notfound; /* Number of failed keydb_search calls. */
unsigned int notfound_cached; /* Ditto but from the cache. */
} keydb_stats;
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
/* Check whether the keyid KID is in key id is definitely not in the
database.
Returns:
0 - Indeterminate: the key id is not in the cache; we don't know
whether the key is in the database or not. If you want a
definitive answer, you'll need to perform a lookup.
1 - There is definitely no key with this key id in the database.
We searched for a key with this key id previously, but we
didn't find it in the database. */
static int
kid_not_found_p (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
for (k = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
{
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => not in DB\n",
(ulong)kid[0], (ulong)kid[1]);
return 1;
}
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_p (%08lx%08lx) => indeterminate\n",
(ulong)kid[0], (ulong)kid[1]);
return 0;
}
/* Insert the keyid KID into the kid_not_found_cache. FOUND is whether
the key is in the key database or not.
Note this function does not check whether the key id is already in
the cache. As such, kid_not_found_p() should be called first. */
static void
kid_not_found_insert (u32 *kid)
{
struct kid_not_found_cache_bucket *k;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_insert (%08lx%08lx)\n",
(ulong)kid[0], (ulong)kid[1]);
k = xmalloc (sizeof *k);
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS];
kid_not_found_cache[kid[0] % KID_NOT_FOUND_CACHE_BUCKETS] = k;
kid_not_found_stats.count++;
}
/* Flush the kid not found cache. */
static void
kid_not_found_flush (void)
{
struct kid_not_found_cache_bucket *k, *knext;
int i;
if (DBG_CACHE)
log_debug ("keydb: kid_not_found_flush\n");
if (!kid_not_found_stats.count)
return;
for (i=0; i < DIM(kid_not_found_cache); i++)
{
for (k = kid_not_found_cache[i]; k; k = knext)
{
knext = k->next;
xfree (k);
}
kid_not_found_cache[i] = NULL;
}
if (kid_not_found_stats.count > kid_not_found_stats.peak)
kid_not_found_stats.peak = kid_not_found_stats.count;
kid_not_found_stats.count = 0;
kid_not_found_stats.flushes++;
}
static void
keyblock_cache_clear (struct keydb_handle_s *hd)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_EMPTY;
iobuf_close (hd->keyblock_cache.iobuf);
hd->keyblock_cache.iobuf = NULL;
hd->keyblock_cache.resource = -1;
hd->keyblock_cache.offset = -1;
}
/* Handle the creation of a keyring or a keybox if it does not yet
exist. Take into account that other processes might have the
keyring/keybox already locked. This lock check does not work if
the directory itself is not yet available. If IS_BOX is true the
filename is expected to refer to a keybox. If FORCE_CREATE is true
the keyring or keybox will be created.
Return 0 if it is okay to access the specified file. */
static gpg_error_t
maybe_create_keyring_or_box (char *filename, int is_box, int force_create)
{
dotlock_t lockhd = NULL;
IOBUF iobuf;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
char *bak_fname = NULL;
char *tmp_fname = NULL;
int save_slash;
/* A quick test whether the filename already exists. */
if (!access (filename, F_OK))
return !access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force_create)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for certain home
directory name pattern. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if (access (filename, F_OK))
{
rc = gpg_error_from_syserror ();
*last_slash_in_filename = save_slash;
goto leave;
}
}
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keyring (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
rc = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (rc));
if (!force_create)
return gpg_error (GPG_ERR_ENOENT); /* Won't happen. */
else
return rc;
}
if ( dotlock_take (lockhd, -1) )
{
rc = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (rc));
goto leave;
}
/* Now the real test while we are locked. */
/* Gpg either uses pubring.gpg or pubring.kbx and thus different
* lock files. Now, when one gpg process is updating a pubring.gpg
* and thus holding the corresponding lock, a second gpg process may
* get to here at the time between the two rename operation used by
* the first process to update pubring.gpg. The lock taken above
* may not protect the second process if it tries to create a
* pubring.kbx file which would be protected by a different lock
* file.
*
* We can detect this case by checking that the two temporary files
* used by the update code exist at the same time. In that case we
* do not create a new file but act as if FORCE_CREATE has not been
* given. Obviously there is a race between our two checks but the
* worst thing is that we won't create a new file, which is better
* than to accidentally creating one. */
rc = keybox_tmp_names (filename, is_box, &bak_fname, &tmp_fname);
if (rc)
goto leave;
if (!access (filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
if (!access (bak_fname, F_OK) && !access (tmp_fname, F_OK))
{
/* Very likely another process is updating a pubring.gpg and we
should not create a pubring.kbx. */
rc = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
if (is_secured_filename (filename))
{
iobuf = NULL;
gpg_err_set_errno (EPERM);
}
else
iobuf = iobuf_create (filename, 0);
umask (oldmask);
if (!iobuf)
{
rc = gpg_error_from_syserror ();
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
iobuf_close (iobuf);
/* Must invalidate that ugly cache */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, filename);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic will work the next time it is used. */
if (is_box)
{
FILE *fp = fopen (filename, "wb");
if (!fp)
rc = gpg_error_from_syserror ();
else
{
rc = _keybox_write_header_blob (fp, NULL, 1);
fclose (fp);
}
if (rc)
{
if (is_box)
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
else
log_error (_("error creating keyring '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
}
if (!opt.quiet)
{
if (is_box)
log_info (_("keybox '%s' created\n"), filename);
else
log_info (_("keyring '%s' created\n"), filename);
}
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
xfree (bak_fname);
xfree (tmp_fname);
return rc;
}
/* Helper for keydb_add_resource. Opens FILENAME to figure out the
resource type.
Returns the specified file's likely type. If the file does not
exist, returns KEYDB_RESOURCE_TYPE_NONE and sets *R_FOUND to 0.
Otherwise, tries to figure out the file's type. This is either
KEYDB_RESOURCE_TYPE_KEYBOX, KEYDB_RESOURCE_TYPE_KEYRING or
KEYDB_RESOURCE_TYPE_KEYNONE. If the file is a keybox and it has
the OpenPGP flag set, then R_OPENPGP is also set. */
static KeydbResourceType
rt_from_file (const char *filename, int *r_found, int *r_openpgp)
{
u32 magic;
unsigned char verbuf[4];
FILE *fp;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
*r_found = *r_openpgp = 0;
fp = fopen (filename, "rb");
if (fp)
{
*r_found = 1;
if (fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - not anymore supported. */
else if (fread (&verbuf, 4, 1, fp) == 1
&& verbuf[0] == 1
&& fread (&magic, 4, 1, fp) == 1
&& !memcmp (&magic, "KBXf", 4))
{
if ((verbuf[3] & 0x02))
*r_openpgp = 1;
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else
rt = KEYDB_RESOURCE_TYPE_KEYRING;
}
else /* Maybe empty: assume keyring. */
rt = KEYDB_RESOURCE_TYPE_KEYRING;
fclose (fp);
}
return rt;
}
char *
keydb_search_desc_dump (struct keydb_search_desc *desc)
{
char b[MAX_FORMATTED_FINGERPRINT_LEN + 1];
char fpr[2 * MAX_FINGERPRINT_LEN + 1];
#if MAX_FINGERPRINT_LEN < UBID_LEN || MAX_FINGERPRINT_LEN < KEYGRIP_LEN
#error MAX_FINGERPRINT_LEN is shorter than KEYGRIP or UBID length.
#endif
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
return xasprintf ("EXACT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SUBSTR:
return xasprintf ("SUBSTR: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAIL:
return xasprintf ("MAIL: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILSUB:
return xasprintf ("MAILSUB: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILEND:
return xasprintf ("MAILEND: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_WORDS:
return xasprintf ("WORDS: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SHORT_KID:
return xasprintf ("SHORT_KID: '%s'",
format_keyid (desc->u.kid, KF_SHORT, b, sizeof (b)));
case KEYDB_SEARCH_MODE_LONG_KID:
return xasprintf ("LONG_KID: '%s'",
format_keyid (desc->u.kid, KF_LONG, b, sizeof (b)));
case KEYDB_SEARCH_MODE_FPR:
bin2hex (desc->u.fpr, desc->fprlen, fpr);
return xasprintf ("FPR%02d: '%s'", desc->fprlen,
format_hexfingerprint (fpr, b, sizeof (b)));
case KEYDB_SEARCH_MODE_ISSUER:
return xasprintf ("ISSUER: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_ISSUER_SN:
return xasprintf ("ISSUER_SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SN:
return xasprintf ("SN: '%*s'",
(int) (desc->snlen == -1
? strlen (desc->sn) : desc->snlen),
desc->sn);
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
bin2hex (desc[0].u.grip, KEYGRIP_LEN, fpr);
return xasprintf ("KEYGRIP: %s", fpr);
case KEYDB_SEARCH_MODE_UBID:
bin2hex (desc[0].u.ubid, UBID_LEN, fpr);
return xasprintf ("UBID: %s", fpr);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
return xasprintf ("NEXT");
default:
return xasprintf ("Bad search mode (%d)", desc->mode);
}
}
/* Register a resource (keyring or keybox). The first keyring or
* keybox that is added using this function is created if it does not
* already exist and the KEYDB_RESOURCE_FLAG_READONLY is not set.
*
* FLAGS are a combination of the KEYDB_RESOURCE_FLAG_* constants.
*
* URL must have the following form:
*
* gnupg-ring:filename = plain keyring
* gnupg-kbx:filename = keybox file
* filename = check file's type (create as a plain keyring)
*
* Note: on systems with drive letters (Windows) invalid URLs (i.e.,
* those with an unrecognized part before the ':' such as "c:\...")
* will silently be treated as bare filenames. On other systems, such
* URLs will cause this function to return GPG_ERR_GENERAL.
*
* If KEYDB_RESOURCE_FLAG_DEFAULT is set, the resource is a keyring
* and the file ends in ".gpg", then this function also checks if a
* file with the same name, but the extension ".kbx" exists, is a
* keybox and the OpenPGP flag is set. If so, this function opens
* that resource instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_GPGVDEF is set and
* the URL ends in ".kbx", then this function will try opening the
* same URL, but with the extension ".gpg". If that file is a keybox
* with the OpenPGP flag set or it is a keyring, then we use that
* instead.
*
* If the file is not found, KEYDB_RESOURCE_FLAG_DEFAULT is set, the
* file should be created and the file's extension is ".gpg" then we
* replace the extension with ".kbx".
*
* If the KEYDB_RESOURCE_FLAG_PRIMARY is set and the resource is a
* keyring (not a keybox), then this resource is considered the
* primary resource. This is used by keydb_locate_writable(). If
* another primary keyring is set, then that keyring is considered the
* primary.
*
* If KEYDB_RESOURCE_FLAG_READONLY is set and the resource is a
* keyring (not a keybox), then the keyring is marked as read only and
* operations just as keyring_insert_keyblock will return
* GPG_ERR_ACCESS. */
gpg_error_t
keydb_add_resource (const char *url, unsigned int flags)
{
/* The file named by the URL (i.e., without the prototype). */
const char *resname = url;
char *filename = NULL;
int create;
int read_only = !!(flags&KEYDB_RESOURCE_FLAG_READONLY);
int is_default = !!(flags&KEYDB_RESOURCE_FLAG_DEFAULT);
int is_gpgvdef = !!(flags&KEYDB_RESOURCE_FLAG_GPGVDEF);
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
void *token;
/* Create the resource if it is the first registered one. */
create = (!read_only && !any_registered);
if (strlen (resname) > 11 && !strncmp( resname, "gnupg-ring:", 11) )
{
rt = KEYDB_RESOURCE_TYPE_KEYRING;
resname += 11;
}
else if (strlen (resname) > 10 && !strncmp (resname, "gnupg-kbx:", 10) )
{
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
resname += 10;
}
#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
else if (strchr (resname, ':'))
{
log_error ("invalid key resource URL '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
if (*resname != DIRSEP_C
#ifdef HAVE_W32_SYSTEM
&& *resname != '/' /* Fixme: does not handle drive letters. */
#endif
)
{
/* Do tilde expansion etc. */
if (strchr (resname, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (resname, '/') /* Windows also accepts this. */
#endif
)
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
/* See whether we can determine the filetype. */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
int found, openpgp_flag;
int pass = 0;
size_t filenamelen;
check_again:
filenamelen = strlen (filename);
rt = rt_from_file (filename, &found, &openpgp_flag);
if (found)
{
/* The file exists and we have the resource type in RT.
Now let us check whether in addition to the "pubring.gpg"
a "pubring.kbx with openpgp keys exists. This is so that
GPG 2.1 will use an existing "pubring.kbx" by default iff
that file has been created or used by 2.1. This check is
needed because after creation or use of the kbx file with
2.1 an older version of gpg may have created a new
pubring.gpg for its own use. */
if (!pass && is_default && rt == KEYDB_RESOURCE_TYPE_KEYRING
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
strcpy (filename+filenamelen-4, ".kbx");
if ((rt_from_file (filename, &found, &openpgp_flag)
== KEYDB_RESOURCE_TYPE_KEYBOX) && found && openpgp_flag)
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".gpg");
}
}
else if (!pass && is_gpgvdef
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".kbx"))
{
/* Not found but gpgv's default "trustedkeys.kbx" file has
been requested. We did not found it so now check whether
a "trustedkeys.gpg" file exists and use that instead. */
KeydbResourceType rttmp;
strcpy (filename+filenamelen-4, ".gpg");
rttmp = rt_from_file (filename, &found, &openpgp_flag);
if (found
&& ((rttmp == KEYDB_RESOURCE_TYPE_KEYBOX && openpgp_flag)
|| (rttmp == KEYDB_RESOURCE_TYPE_KEYRING)))
rt = rttmp;
else /* Restore filename */
strcpy (filename+filenamelen-4, ".kbx");
}
else if (!pass
&& is_default && create
&& filenamelen > 4 && !strcmp (filename+filenamelen-4, ".gpg"))
{
/* The file does not exist, the default resource has been
requested, the file shall be created, and the file has a
".gpg" suffix. Change the suffix to ".kbx" and try once
more. This way we achieve that we open an existing
".gpg" keyring, but create a new keybox file with an
".kbx" suffix. */
strcpy (filename+filenamelen-4, ".kbx");
pass++;
goto check_again;
}
else /* No file yet: create keybox. */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = maybe_create_keyring_or_box (filename, 0, create);
if (err)
goto leave;
if (keyring_register_filename (filename, read_only, &token))
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
used_resources++;
}
}
else
{
/* This keyring was already registered, so ignore it.
However, we can still mark it as primary even if it was
already registered. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
err = maybe_create_keyring_or_box (filename, 1, create);
if (err)
goto leave;
err = keybox_register_file (filename, 0, &token);
if (!err)
{
if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
KEYBOX_HANDLE kbxhd;
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kb = NULL; /* Not used here */
all_resources[used_resources].token = token;
/* Do a compress run if needed and no other user is
* currently using the keybox. */
kbxhd = keybox_new_openpgp (token, 0);
if (kbxhd)
{
if (!keybox_lock (kbxhd, 1, 0))
{
keybox_compress (kbxhd);
keybox_lock (kbxhd, 0, 0);
}
keybox_release (kbxhd);
}
used_resources++;
}
}
else if (gpg_err_code (err) == GPG_ERR_EEXIST)
{
/* Already registered. We will mark it as the primary key
if requested. */
if ((flags & KEYDB_RESOURCE_FLAG_PRIMARY))
primary_keydb = token;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
log_error (_("keyblock resource '%s': %s\n"),
filename, gpg_strerror (err));
write_status_error ("add_keyblock_resource", err);
}
else
any_registered = 1;
xfree (filename);
return err;
}
void
keydb_dump_stats (void)
{
log_info ("keydb: handles=%u locks=%u parse=%u get=%u\n",
keydb_stats.handles,
keydb_stats.locks,
keydb_stats.parse_keyblocks,
keydb_stats.get_keyblocks);
log_info (" build=%u update=%u insert=%u delete=%u\n",
keydb_stats.build_keyblocks,
keydb_stats.update_keyblocks,
keydb_stats.insert_keyblocks,
keydb_stats.delete_keyblocks);
log_info (" reset=%u found=%u not=%u cache=%u not=%u\n",
keydb_stats.search_resets,
keydb_stats.found,
keydb_stats.notfound,
keydb_stats.found_cached,
keydb_stats.notfound_cached);
log_info ("kid_not_found_cache: count=%u peak=%u flushes=%u\n",
kid_not_found_stats.count,
kid_not_found_stats.peak,
kid_not_found_stats.flushes);
}
/* keydb_new diverts to here in non-keyboxd mode. HD is just the
- * calloced structure with the handle type intialized. */
+ * calloced structure with the handle type initialized. */
gpg_error_t
internal_keydb_init (KEYDB_HANDLE hd)
{
gpg_error_t err = 0;
int i, j;
int die = 0;
int reterrno;
log_assert (!hd->use_keyboxd);
hd->found = -1;
hd->saved_found = -1;
hd->is_reset = 1;
log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; ! die && i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kr = keyring_new (all_resources[i].token);
if (!hd->active[j].u.kr)
{
reterrno = errno;
die = 1;
}
j++;
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kb = keybox_new_openpgp (all_resources[i].token, 0);
if (!hd->active[j].u.kb)
{
reterrno = errno;
die = 1;
}
j++;
break;
}
}
hd->used = j;
active_handles++;
keydb_stats.handles++;
if (die)
err = gpg_error_from_errno (reterrno);
return err;
}
/* Free all non-keyboxd resources owned by the database handle.
* keydb_release diverts to here. */
void
internal_keydb_deinit (KEYDB_HANDLE hd)
{
int i;
log_assert (!hd->use_keyboxd);
log_assert (active_handles > 0);
active_handles--;
hd->keep_lock = 0;
unlock_all (hd);
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_release (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kb);
break;
}
}
keyblock_cache_clear (hd);
}
/* Take a lock on the files immediately and not only during insert or
* update. This lock is released with keydb_release. */
gpg_error_t
internal_keydb_lock (KEYDB_HANDLE hd)
{
gpg_error_t err;
log_assert (!hd->use_keyboxd);
err = lock_all (hd);
if (!err)
hd->keep_lock = 1;
return err;
}
/* Set a flag on the handle to suppress use of cached results. This
* is required for updating a keyring and for key listings. Fixme:
* Using a new parameter for keydb_new might be a better solution. */
void
keydb_disable_caching (KEYDB_HANDLE hd)
{
if (hd && !hd->use_keyboxd)
hd->no_caching = 1;
}
/* Return the file name of the resource in which the current search
* result was found or, if there is no search result, the filename of
* the current resource (i.e., the resource that the file position
* points to). Note: the filename is not necessarily the URL used to
* open it!
*
* This function only returns NULL if no handle is specified, in all
* other error cases an empty string is returned. */
const char *
keydb_get_resource_name (KEYDB_HANDLE hd)
{
int idx;
const char *s = NULL;
if (!hd)
return NULL;
if (hd->use_keyboxd)
return "[keyboxd]";
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
idx = 0;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
s = NULL;
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
s = keyring_get_resource_name (hd->active[idx].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kb);
break;
}
return s? s: "";
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
/* Fixme: This locking scheme may lead to a deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem.
[Oops: Who claimed the latter]
To fix this we need to use a lock file to protect lock_all. */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_lock (hd->active[i].u.kr, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_lock (hd->active[i].u.kb, 1, -1);
break;
}
}
if (rc)
{
/* Revert the already taken locks. */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kb, 0, 0);
break;
}
}
}
else
{
hd->locked = 1;
keydb_stats.locks++;
}
return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (!hd->locked || hd->keep_lock)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_lock (hd->active[i].u.kr, 0);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kb, 0, 0);
break;
}
}
hd->locked = 0;
}
/* Save the last found state and invalidate the current selection
* (i.e., the entry selected by keydb_search() is invalidated and
* something like keydb_get_keyblock() will return an error). This
* does not change the file position. This makes it possible to do
* something like:
*
* keydb_search (hd, ...); // Result 1.
* keydb_push_found_state (hd);
* keydb_search_reset (hd);
* keydb_search (hd, ...); // Result 2.
* keydb_pop_found_state (hd);
* keydb_get_keyblock (hd, ...); // -> Result 1.
*
* Note: it is only possible to save a single save state at a time.
* In other words, the save stack only has room for a single
* instance of the state. */
/* FIXME(keyboxd): This function is used only at one place - see how
* we can avoid it. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->found < 0 || hd->found >= hd->used)
{
hd->saved_found = -1;
return;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_push_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kb);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
}
/* Restore the previous save state. If the saved state is NULL or
invalid, this is a NOP. */
/* FIXME(keyboxd): This function is used only at one place - see how
* we can avoid it. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
return;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
keyring_pop_found_state (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kb);
break;
}
}
static gpg_error_t
parse_keyblock_image (iobuf_t iobuf, int pk_no, int uid_no,
kbnode_t *r_keyblock)
{
gpg_error_t err;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t keyblock = NULL;
kbnode_t node, *tail;
int in_cert, save_mode;
int pk_count, uid_count;
*r_keyblock = NULL;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
return gpg_error_from_syserror ();
init_packet (pkt);
init_parse_packet (&parsectx, iobuf);
save_mode = set_packet_list_mode (0);
in_cert = 0;
tail = NULL;
pk_count = uid_count = 0;
while ((err = parse_packet (&parsectx, pkt)) != -1)
{
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_PACKET)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (err)
{
es_fflush (es_stdout);
log_error ("parse_keyblock_image: read error: %s\n",
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_INV_PACKET)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
case PKT_RING_TRUST:
break; /* Allowed per RFC. */
default:
log_info ("skipped packet of type %d in keybox\n", (int)pkt->pkttype);
free_packet(pkt, &parsectx);
init_packet(pkt);
continue;
}
/* Other sanity checks. */
if (!in_cert && pkt->pkttype != PKT_PUBLIC_KEY)
{
log_error ("parse_keyblock_image: first packet in a keybox blob "
"is not a public key packet\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
log_error ("parse_keyblock_image: "
"multiple keyblocks in a keybox blob\n");
err = gpg_error (GPG_ERR_INV_KEYRING);
break;
}
in_cert = 1;
node = new_kbnode (pkt);
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_count == pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_count == uid_no)
node->flag |= 2;
break;
default:
break;
}
if (!keyblock)
keyblock = node;
else
*tail = node;
tail = &node->next;
pkt = xtrymalloc (sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
break;
}
init_packet (pkt);
}
set_packet_list_mode (save_mode);
if (err == -1 && keyblock)
err = 0; /* Got the entire keyblock. */
if (err)
release_kbnode (keyblock);
else
{
*r_keyblock = keyblock;
keydb_stats.parse_keyblocks++;
}
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
return err;
}
/* Return the keyblock last found by keydb_search() in *RET_KB.
* keydb_get_keyblock divert to here in the non-keyboxd mode.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
* code.
*
* The returned keyblock has the kbnode flag bit 0 set for the node
* with the public key used to locate the keyblock or flag bit 1 set
* for the user ID node. */
gpg_error_t
internal_keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb)
{
gpg_error_t err = 0;
log_assert (!hd->use_keyboxd);
if (hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED)
{
err = iobuf_seek (hd->keyblock_cache.iobuf, 0);
if (err)
{
log_error ("keydb_get_keyblock: failed to rewind iobuf for cache\n");
keyblock_cache_clear (hd);
}
else
{
err = parse_keyblock_image (hd->keyblock_cache.iobuf,
hd->keyblock_cache.pk_no,
hd->keyblock_cache.uid_no,
ret_kb);
if (err)
keyblock_cache_clear (hd);
if (DBG_CLOCK)
log_clock ("%s leave (cached mode)", __func__);
return err;
}
}
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_get_keyblock (hd->active[hd->found].u.kr, ret_kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
int pk_no, uid_no;
err = keybox_get_keyblock (hd->active[hd->found].u.kb,
&iobuf, &pk_no, &uid_no);
if (!err)
{
err = parse_keyblock_image (iobuf, pk_no, uid_no, ret_kb);
if (!err && hd->keyblock_cache.state == KEYBLOCK_CACHE_PREPARED)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_FILLED;
hd->keyblock_cache.iobuf = iobuf;
hd->keyblock_cache.pk_no = pk_no;
hd->keyblock_cache.uid_no = uid_no;
}
else
{
iobuf_close (iobuf);
}
}
}
break;
}
if (hd->keyblock_cache.state != KEYBLOCK_CACHE_FILLED)
keyblock_cache_clear (hd);
if (!err)
keydb_stats.get_keyblocks++;
return err;
}
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
* keydb_update_keyblock diverts to here in the non-keyboxd mode.
*
* This doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. Note:
* if there isn't a keyblock in the keyring corresponding to KB, then
* this function returns GPG_ERR_VALUE_NOT_FOUND.
*
* This function selects the matching record and modifies the current
* file position to point to the record just after the selected entry.
* Thus, if you do a subsequent search using HD, you should first do a
* keydb_search_reset. Further, if the selected record is important,
* you should use keydb_push_found_state and keydb_pop_found_state to
* save and restore it. */
gpg_error_t
internal_keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
PKT_public_key *pk;
KEYDB_SEARCH_DESC desc;
size_t len;
log_assert (!hd->use_keyboxd);
pk = kb->pkt->pkt.public_key;
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (opt.dry_run)
return 0;
err = lock_all (hd);
if (err)
return err;
#ifdef USE_TOFU
tofu_notice_key_changed (ctrl, kb);
#endif
memset (&desc, 0, sizeof (desc));
fingerprint_from_pk (pk, desc.u.fpr, &len);
if (len == 20 || len == 32)
{
desc.mode = KEYDB_SEARCH_MODE_FPR;
desc.fprlen = len;
}
else
log_bug ("%s: Unsupported key length: %zu\n", __func__, len);
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (err)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
log_assert (hd->found >= 0 && hd->found < hd->used);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_update_keyblock (hd->active[hd->found].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{
iobuf_t iobuf;
err = build_keyblock_image (kb, &iobuf);
if (!err)
{
keydb_stats.build_keyblocks++;
err = keybox_update_keyblock (hd->active[hd->found].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf));
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
if (!err)
keydb_stats.update_keyblocks++;
return err;
}
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
* keydb_insert_keyblock diverts to here in the non-keyboxd mode.
*
* Be default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
* keydb_search_reset was called), then the keyring / keybox where the
* next search would start is used (i.e., the current file position).
*
* Note: this doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
internal_keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
int idx;
log_assert (!hd->use_keyboxd);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (opt.dry_run)
return 0;
if (hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if (hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
return gpg_error (GPG_ERR_GENERAL);
err = lock_all (hd);
if (err)
return err;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
err = keyring_insert_keyblock (hd->active[idx].u.kr, kb);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
{ /* We need to turn our kbnode_t list of packets into a proper
keyblock first. This is required by the OpenPGP key parser
included in the keybox code. Eventually we can change this
kludge to have the caller pass the image. */
iobuf_t iobuf;
err = build_keyblock_image (kb, &iobuf);
if (!err)
{
keydb_stats.build_keyblocks++;
err = keybox_insert_keyblock (hd->active[idx].u.kb,
iobuf_get_temp_buffer (iobuf),
iobuf_get_temp_length (iobuf));
iobuf_close (iobuf);
}
}
break;
}
unlock_all (hd);
if (!err)
keydb_stats.insert_keyblocks++;
return err;
}
/* Delete the currently selected keyblock. If you haven't done a
* search yet on this database handle (or called keydb_search_reset),
* then this will return an error.
*
* Returns 0 on success or an error code, if an error occurs. */
gpg_error_t
internal_keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t rc;
log_assert (!hd->use_keyboxd);
kid_not_found_flush ();
keyblock_cache_clear (hd);
if (hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_VALUE_NOT_FOUND);
if (opt.dry_run)
return 0;
rc = lock_all (hd);
if (rc)
return rc;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
rc = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_delete_keyblock (hd->active[hd->found].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_delete (hd->active[hd->found].u.kb);
break;
}
unlock_all (hd);
if (!rc)
keydb_stats.delete_keyblocks++;
return rc;
}
/* A database may consists of multiple keyrings / key boxes. This
* sets the "file position" to the start of the first keyring / key
* box that is writable (i.e., doesn't have the read-only flag set).
*
* This first tries the primary keyring (the last keyring (not
* keybox!) added using keydb_add_resource() and with
* KEYDB_RESOURCE_FLAG_PRIMARY set). If that is not writable, then it
* tries the keyrings / keyboxes in the order in which they were
* added. */
gpg_error_t
keydb_locate_writable (KEYDB_HANDLE hd)
{
gpg_error_t rc;
if (!hd)
return GPG_ERR_INV_ARG;
if (hd->use_keyboxd)
return 0; /* No need for this here. */
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
/* If we have a primary set, try that one first */
if (primary_keydb)
{
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
if(hd->active[hd->current].token == primary_keydb)
{
if(keyring_is_writable (hd->active[hd->current].token))
return 0;
else
break;
}
}
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
}
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG();
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
if (keyring_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (keybox_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
}
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Rebuild the on-disk caches of all key resources. */
void
keydb_rebuild_caches (ctrl_t ctrl, int noisy)
{
int i, rc;
if (opt.use_keyboxd)
return; /* No need for this here. */
for (i=0; i < used_resources; i++)
{
if (!keyring_is_writable (all_resources[i].token))
continue;
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_rebuild_cache (ctrl, all_resources[i].token,noisy);
if (rc)
log_error (_("failed to rebuild keyring cache: %s\n"),
gpg_strerror (rc));
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* N/A. */
break;
}
}
}
/* Return the number of skipped blocks (because they were too large to
read from a keybox) since the last search reset. */
unsigned long
keydb_get_skipped_counter (KEYDB_HANDLE hd)
{
/*FIXME(keyboxd): Do we need this? */
return hd && !hd->use_keyboxd? hd->skipped_long_blobs : 0;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database
* (the start of the first resource).
* keydb_search_reset diverts to here in the non-keyboxd mode.
*
* Returns 0 on success and an error code if an error occurred.
* (Currently, this function always returns 0 if HD is valid.) */
gpg_error_t
internal_keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t rc = 0;
int i;
log_assert (!hd->use_keyboxd);
keyblock_cache_clear (hd);
hd->skipped_long_blobs = 0;
hd->current = 0;
hd->found = -1;
/* Now reset all resources. */
for (i=0; !rc && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_search_reset (hd->active[i].u.kr);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_search_reset (hd->active[i].u.kb);
break;
}
}
hd->is_reset = 1;
if (!rc)
keydb_stats.search_resets++;
return rc;
}
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
* keydb_search diverts to here in the non-keyboxd mode.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, returns
* GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
* occurred, returns an error code.
*
* The returned key is considered to be selected and the raw data can,
* for instance, be returned by calling keydb_get_keyblock(). */
gpg_error_t
internal_keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex)
{
gpg_error_t rc;
int was_reset = hd->is_reset;
/* If an entry is already in the cache, then don't add it again. */
int already_in_cache = 0;
int fprlen;
log_assert (!hd->use_keyboxd);
if (!any_registered)
{
write_status_error ("keydb_search", gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& (already_in_cache = kid_not_found_p (desc[0].u.kid)) == 1 )
{
if (DBG_CLOCK)
log_clock ("%s leave (not found, cached)", __func__);
keydb_stats.notfound_cached++;
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* NB: If one of the exact search modes below is used in a loop to
walk over all keys (with the same fingerprint) the caching must
have been disabled for the handle. */
if (desc[0].mode == KEYDB_SEARCH_MODE_FPR)
fprlen = desc[0].fprlen;
else
fprlen = 0;
if (!hd->no_caching
&& ndesc == 1
&& fprlen
&& hd->keyblock_cache.state == KEYBLOCK_CACHE_FILLED
&& hd->keyblock_cache.fprlen == fprlen
&& !memcmp (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen)
/* Make sure the current file position occurs before the cached
result to avoid an infinite loop. */
&& (hd->current < hd->keyblock_cache.resource
|| (hd->current == hd->keyblock_cache.resource
&& (keybox_offset (hd->active[hd->current].u.kb)
<= hd->keyblock_cache.offset))))
{
/* (DESCINDEX is already set). */
if (DBG_CLOCK)
log_clock ("%s leave (cached)", __func__);
hd->current = hd->keyblock_cache.resource;
/* HD->KEYBLOCK_CACHE.OFFSET is the last byte in the record.
Seek just beyond that. */
keybox_seek (hd->active[hd->current].u.kb, hd->keyblock_cache.offset + 1);
keydb_stats.found_cached++;
return 0;
}
rc = -1;
while ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
&& hd->current >= 0 && hd->current < hd->used)
{
if (DBG_LOOKUP)
log_debug ("%s: searching %s (resource %d of %d)\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
hd->current, hd->used);
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG(); /* we should never see it here */
break;
case KEYDB_RESOURCE_TYPE_KEYRING:
rc = keyring_search (hd->active[hd->current].u.kr, desc,
ndesc, descindex, 1);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
do
rc = keybox_search (hd->active[hd->current].u.kb, desc,
ndesc, KEYBOX_BLOBTYPE_PGP,
descindex, &hd->skipped_long_blobs);
while (rc == GPG_ERR_LEGACY_KEY);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (resource %d of %d) => %s\n",
__func__,
hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYRING
? "keyring"
: (hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type"),
hd->current, hd->used,
rc == -1 ? "EOF" : gpg_strerror (rc));
if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
/* EOF -> switch to next resource */
hd->current++;
}
else if (!rc)
hd->found = hd->current;
}
hd->is_reset = 0;
rc = ((rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
? gpg_error (GPG_ERR_NOT_FOUND)
: rc);
keyblock_cache_clear (hd);
if (!hd->no_caching
&& !rc
&& ndesc == 1
&& fprlen
&& hd->active[hd->current].type == KEYDB_RESOURCE_TYPE_KEYBOX)
{
hd->keyblock_cache.state = KEYBLOCK_CACHE_PREPARED;
hd->keyblock_cache.resource = hd->current;
/* The current offset is at the start of the next record. Since
a record is at least 1 byte, we just use offset - 1, which is
within the record. */
hd->keyblock_cache.offset
= keybox_offset (hd->active[hd->current].u.kb) - 1;
memcpy (hd->keyblock_cache.fpr, desc[0].u.fpr, fprlen);
hd->keyblock_cache.fprlen = fprlen;
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND
&& ndesc == 1
&& desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID
&& was_reset
&& !already_in_cache)
kid_not_found_insert (desc[0].u.kid);
if (!rc)
keydb_stats.found++;
else
keydb_stats.notfound++;
return rc;
}
/* Return the first non-legacy key in the database.
*
* If you want the very first key in the database, you can directly
* call keydb_search with the search description
* KEYDB_SEARCH_MODE_FIRST. */
gpg_error_t
keydb_search_first (KEYDB_HANDLE hd)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
err = keydb_search_reset (hd);
if (err)
return err;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (hd, &desc, 1, NULL);
}
/* Return the next key (not the next matching key!).
*
* Unlike calling keydb_search with KEYDB_SEARCH_MODE_NEXT, this
* function silently skips legacy keys. */
gpg_error_t
keydb_search_next (KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (hd, &desc, 1, NULL);
}
/* This is a convenience function for searching for keys with a long
* key id.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
keydb_search_kid (KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = kid[0];
desc.u.kid[1] = kid[1];
return keydb_search (hd, &desc, 1, NULL);
}
/* This is a convenience function for searching for keys with a long
* (20 byte) fingerprint.
*
* Note: this function resumes searching where the last search left
* off. If you want to search the whole database, then you need to
* first call keydb_search_reset(). */
gpg_error_t
keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr, size_t fprlen)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, fprlen);
desc.fprlen = fprlen;
return keydb_search (hd, &desc, 1, NULL);
}
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 1391b6b7a..ac9f4688c 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -1,6278 +1,6278 @@
/* keyedit.c - Edit properties of a key
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2017 Werner Koch
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "photoid.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/host2net.h"
#include "tofu.h"
#include "key-check.h"
#include "key-clean.h"
#include "keyedit.h"
static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
int verbose);
static void show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk,
unsigned int flag, int with_prefs);
static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked,
int with_revoker, int with_fpr,
int with_subkeys, int with_prefs,
int nowarn);
static void show_key_and_fingerprint (ctrl_t ctrl,
kbnode_t keyblock, int with_subkeys);
static void show_key_and_grip (kbnode_t keyblock);
static void subkey_expire_warning (kbnode_t keyblock);
static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock,
int photo, const char *photo_name, const char *uidstr);
static void menu_deluid (KBNODE pub_keyblock);
static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only);
static void menu_delkey (KBNODE pub_keyblock);
static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration);
static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock);
static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_keyserver_url (ctrl_t ctrl,
const char *url, kbnode_t pub_keyblock);
static int menu_set_notation (ctrl_t ctrl,
const char *string, kbnode_t pub_keyblock);
static int menu_select_uid (KBNODE keyblock, int idx);
static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash);
static int menu_select_key (KBNODE keyblock, int idx, char *p);
static int count_uids (KBNODE keyblock);
static int count_uids_with_flag (KBNODE keyblock, unsigned flag);
static int count_keys_with_flag (KBNODE keyblock, unsigned flag);
static int count_selected_uids (KBNODE keyblock);
static int real_uids_left (KBNODE keyblock);
static int count_selected_keys (KBNODE keyblock);
static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock);
static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock);
static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason,
int *modified);
static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock);
#ifndef NO_TRUST_MODELS
static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable);
#endif /*!NO_TRUST_MODELS*/
static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock);
static int update_trust = 0;
#define CONTROL_D ('D' - 'A' + 1)
struct sign_attrib
{
int non_exportable, non_revocable;
struct revocation_reason_info *reason;
byte trust_depth, trust_value;
char *trust_regexp;
};
/* TODO: Fix duplicated code between here and the check-sigs/list-sigs
code in keylist.c. */
static int
print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key)
{
PKT_signature *sig = node->pkt->pkt.signature;
int rc, sigrc;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:",
sigrc, sig->pubkey_algo, (ulong) sig->keyid[0],
(ulong) sig->keyid[1], (ulong) sig->timestamp,
(ulong) sig->expiredate);
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d", sig->trust_depth, sig->trust_value);
es_printf (":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout,
sig->trust_regexp, strlen (sig->trust_regexp),
":", NULL);
es_printf ("::%02x%c\n", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
return (sigrc == '!');
}
/*
* Print information about a signature (rc is its status), check it
* and return true if the signature is okay. NODE must be a signature
* packet. With EXTENDED set all possible signature list options will
* always be printed.
*/
int
keyedit_print_one_sig (ctrl_t ctrl, estream_t fp,
int rc, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int is_selfsig, int print_without_key, int extended)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
int is_rev = sig->sig_class == 0x30;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
tty_fprintf (fp, "%s%c%c %c%c%c%c%c%c %s %s",
is_rev ? "rev" : "sig", sigrc,
(sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ',
keystr (sig->keyid),
datestr_from_sig (sig));
if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended )
tty_fprintf (fp, " %s", expirestr_from_sig (sig));
tty_fprintf (fp, " ");
if (sigrc == '%')
tty_fprintf (fp, "[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (is_selfsig)
{
tty_fprintf (fp, is_rev ? _("[revocation]") : _("[self-signature]"));
if (extended && sig->flags.chosen_selfsig)
tty_fprintf (fp, "*");
}
else
{
size_t n;
char *p = get_user_id (ctrl, sig->keyid, &n, NULL);
tty_print_utf8_string2 (fp, p, n,
opt.screen_columns - keystrlen () - 26 -
((opt.
list_options & LIST_SHOW_SIG_EXPIRE) ? 11
: 0));
xfree (p);
}
if (fp == log_get_stream ())
log_printf ("\n");
else
tty_fprintf (fp, "\n");
if (sig->flags.policy_url
&& ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended))
show_policy_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (sig->flags.notation
&& ((opt.list_options & LIST_SHOW_NOTATIONS) || extended))
show_notation (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0),
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) +
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended))
show_keyserver_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (extended)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
const unsigned char *s;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL);
if (s && *s)
tty_fprintf (fp, " [primary]\n");
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (s && buf32_to_u32 (s))
tty_fprintf (fp, " [expires: %s]\n",
isotimestamp (pk->timestamp + buf32_to_u32 (s)));
}
}
return (sigrc == '!');
}
static int
print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key, int extended)
{
int rc;
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
return keyedit_print_one_sig (ctrl, NULL, rc,
keyblock, node, inv_sigs, no_key, oth_err,
*is_selfsig, print_without_key, extended);
}
static int
sign_mk_attrib (PKT_signature * sig, void *opaque)
{
struct sign_attrib *attrib = opaque;
byte buf[8];
if (attrib->non_exportable)
{
buf[0] = 0; /* not exportable */
build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1);
}
if (attrib->non_revocable)
{
buf[0] = 0; /* not revocable */
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
}
if (attrib->reason)
revocation_reason_build_cb (sig, attrib->reason);
if (attrib->trust_depth)
{
/* Not critical. If someone doesn't understand trust sigs,
this can still be a valid regular signature. */
buf[0] = attrib->trust_depth;
buf[1] = attrib->trust_value;
build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2);
/* Critical. If someone doesn't understands regexps, this
whole sig should be invalid. Note the +1 for the length -
regexps are null terminated. */
if (attrib->trust_regexp)
build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP,
attrib->trust_regexp,
strlen (attrib->trust_regexp) + 1);
}
return 0;
}
static void
trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp)
{
char *p;
*trust_value = 0;
*trust_depth = 0;
*regexp = NULL;
/* Same string as pkclist.c:do_edit_ownertrust */
tty_printf (_
("Please decide how far you trust this user to correctly verify"
" other users' keys\n(by looking at passports, checking"
" fingerprints from different sources, etc.)\n"));
tty_printf ("\n");
tty_printf (_(" %d = I trust marginally\n"), 1);
tty_printf (_(" %d = I trust fully\n"), 2);
tty_printf ("\n");
while (*trust_value == 0)
{
p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
/* 60 and 120 are as per RFC2440 */
if (p[0] == '1' && !p[1])
*trust_value = 60;
else if (p[0] == '2' && !p[1])
*trust_value = 120;
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter the depth of this trust signature.\n"
"A depth greater than 1 allows the key you are"
" signing to make\n"
"trust signatures on your behalf.\n"));
tty_printf ("\n");
while (*trust_depth == 0)
{
p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
*trust_depth = atoi (p);
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter a domain to restrict this signature, "
"or enter for none.\n"));
tty_printf ("\n");
p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
if (strlen (p) > 0)
{
char *q = p;
int regexplen = 100, ind;
*regexp = xmalloc (regexplen);
/* Now mangle the domain the user entered into a regexp. To do
this, \-escape everything that isn't alphanumeric, and attach
"<[^>]+[@.]" to the front, and ">$" to the end. */
strcpy (*regexp, "<[^>]+[@.]");
ind = strlen (*regexp);
while (*q)
{
if (!((*q >= 'A' && *q <= 'Z')
|| (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9')))
(*regexp)[ind++] = '\\';
(*regexp)[ind++] = *q;
if ((regexplen - ind) < 3)
{
regexplen += 100;
*regexp = xrealloc (*regexp, regexplen);
}
q++;
}
(*regexp)[ind] = '\0';
strcat (*regexp, ">$");
}
xfree (p);
tty_printf ("\n");
}
/*
* Loop over all LOCUSR and sign the uids after asking. If no
* user id is marked, all user ids will be signed; if some user_ids
* are marked only those will be signed. If QUICK is true the
* function won't ask the user and use sensible defaults.
*/
static int
sign_uids (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, strlist_t locusr, int *ret_modified,
int local, int nonrevocable, int trust, int interactive,
int quick)
{
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
PKT_public_key *pk = NULL;
KBNODE node, uidnode;
PKT_public_key *primary_pk = NULL;
int select_all = !count_selected_uids (keyblock) || interactive;
/* Build a list of all signators.
*
* We use the CERT flag to request the primary which must always
* be one which is capable of signing keys. I can't see a reason
* why to sign keys using a subkey. Implementation of USAGE_CERT
* is just a hack in getkey.c and does not mean that a subkey
* marked as certification capable will be used. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if (rc)
goto leave;
/* Loop over all signators. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
u32 sk_keyid[2], pk_keyid[2];
char *p, *trust_regexp = NULL;
int class = 0, selfsig = 0;
u32 duration = 0, timestamp = 0;
byte trust_depth = 0, trust_value = 0;
pk = sk_rover->pk;
keyid_from_pk (pk, sk_keyid);
/* Set mark A for all selected user ids. */
for (node = keyblock; node; node = node->next)
{
if (select_all || (node->flag & NODFLG_SELUID))
node->flag |= NODFLG_MARK_A;
else
node->flag &= ~NODFLG_MARK_A;
}
/* Reset mark for uids which are already signed. */
uidnode = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
primary_pk = node->pkt->pkt.public_key;
keyid_from_pk (primary_pk, pk_keyid);
/* Is this a self-sig? */
if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1])
selfsig = 1;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL;
if (uidnode)
{
int yesreally = 0;
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
if (opt.only_sign_text_ids
&& uidnode->pkt->pkt.user_id->attribs)
{
tty_fprintf (fp, _("Skipping user ID \"%s\","
" which is not a text ID.\n"),
user);
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (uidnode->pkt->pkt.user_id->flags.revoked)
{
tty_fprintf (fp, _("User ID \"%s\" is revoked."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (uidnode->pkt->pkt.user_id->flags.expired)
{
tty_fprintf (fp, _("User ID \"%s\" is expired."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.expire_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (!uidnode->pkt->pkt.user_id->created && !selfsig)
{
tty_fprintf (fp, _("User ID \"%s\" is not self-signed."),
user);
if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
if (uidnode && interactive && !yesreally && !quick)
{
tty_fprintf (fp,
_("User ID \"%s\" is signable. "), user);
if (!cpr_get_answer_is_yes ("sign_uid.sign_okay",
_("Sign it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
}
xfree (user);
}
}
else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class & ~3) == 0x10)
{
if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0]
&& sk_keyid[1] == node->pkt->pkt.signature->keyid[1])
{
char buf[50];
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
/* It's a v3 self-sig. Make it into a v4 self-sig? */
if (node->pkt->pkt.signature->version < 4
&& selfsig && !quick)
{
tty_fprintf (fp,
_("The self-signature on \"%s\"\n"
"is a PGP 2.x-style signature.\n"), user);
/* Note that the regular PGP2 warning below
still applies if there are no v4 sigs on
this key at all. */
if (opt.expert)
if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay",
_("Do you want to promote "
"it to an OpenPGP self-"
"signature? (y/N) ")))
{
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Is the current signature expired? */
if (node->pkt->pkt.signature->flags.expired)
{
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"has expired.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.replace_expired_okay",
_("Do you want to issue a "
"new signature to replace "
"the expired one? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
if (!node->pkt->pkt.signature->flags.exportable && !local)
{
/* It's a local sig, and we want to make a
exportable sig. */
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"is a local signature.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.local_promote_okay",
_("Do you want to promote "
"it to a full exportable " "signature? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Fixme: see whether there is a revocation in which
* case we should allow signing it again. */
if (!node->pkt->pkt.signature->flags.exportable && local)
tty_fprintf ( fp,
_("\"%s\" was already locally signed by key %s\n"),
user, keystr_from_pk (pk));
else
tty_fprintf (fp,
_("\"%s\" was already signed by key %s\n"),
user, keystr_from_pk (pk));
if (opt.expert && !quick
&& cpr_get_answer_is_yes ("sign_uid.dupe_okay",
_("Do you want to sign it "
"again anyway? (y/N) ")))
{
/* Don't delete the old sig here since this is
an --expert thing. */
xfree (user);
continue;
}
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong) pk->keyid[0], (ulong) pk->keyid[1]);
write_status_text (STATUS_ALREADY_SIGNED, buf);
uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */
xfree (user);
}
}
}
/* Check whether any uids are left for signing. */
if (!count_uids_with_flag (keyblock, NODFLG_MARK_A))
{
tty_fprintf (fp, _("Nothing to sign with key %s\n"),
keystr_from_pk (pk));
continue;
}
/* Ask whether we really should sign these user id(s). */
tty_fprintf (fp, "\n");
show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0);
tty_fprintf (fp, "\n");
if (primary_pk->expiredate && !selfsig)
{
/* Static analyzer note: A claim that PRIMARY_PK might be
NULL is not correct because it set from the public key
packet which is always the first packet in a keyblock and
parsed in the above loop over the keyblock. In case the
keyblock has no packets at all and thus the loop was not
entered the above count_uids_with_flag would have
detected this case. */
u32 now = make_timestamp ();
if (primary_pk->expiredate <= now)
{
tty_fprintf (fp, _("This key has expired!"));
if (opt.expert && !quick)
{
tty_fprintf (fp, " ");
if (!cpr_get_answer_is_yes ("sign_uid.expired_okay",
_("Are you sure you still "
"want to sign it? (y/N) ")))
continue;
}
else
{
tty_fprintf (fp, _(" Unable to sign.\n"));
continue;
}
}
else
{
tty_fprintf (fp, _("This key is due to expire on %s.\n"),
expirestr_from_pk (primary_pk));
if (opt.ask_cert_expire && !quick)
{
char *answer = cpr_get ("sign_uid.expire",
_("Do you want your signature to "
"expire at the same time? (Y/n) "));
if (answer_is_yes_no_default (answer, 1))
{
/* This fixes the signature timestamp we're
going to make as now. This is so the
expiration date is exactly correct, and not
a few seconds off (due to the time it takes
to answer the questions, enter the
passphrase, etc). */
timestamp = now;
duration = primary_pk->expiredate - now;
}
cpr_kill_prompt ();
xfree (answer);
}
}
}
/* Only ask for duration if we haven't already set it to match
the expiration of the pk */
if (!duration && !selfsig)
{
if (opt.ask_cert_expire && !quick)
duration = ask_expire_interval (1, opt.def_cert_expire);
else
duration = parse_expire_string (opt.def_cert_expire);
}
if (selfsig)
;
else
{
if (opt.batch || !opt.ask_cert_level || quick)
class = 0x10 + opt.def_cert_level;
else
{
char *answer;
tty_fprintf (fp,
_("How carefully have you verified the key you are "
"about to sign actually belongs\nto the person "
"named above? If you don't know what to "
"answer, enter \"0\".\n"));
tty_fprintf (fp, "\n");
tty_fprintf (fp, _(" (0) I will not answer.%s\n"),
opt.def_cert_level == 0 ? " (default)" : "");
tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"),
opt.def_cert_level == 1 ? " (default)" : "");
tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"),
opt.def_cert_level == 2 ? " (default)" : "");
tty_fprintf (fp,
_(" (3) I have done very careful checking.%s\n"),
opt.def_cert_level == 3 ? " (default)" : "");
tty_fprintf (fp, "\n");
while (class == 0)
{
answer = cpr_get ("sign_uid.class",
_("Your selection? "
"(enter '?' for more information): "));
if (answer[0] == '\0')
class = 0x10 + opt.def_cert_level; /* Default */
else if (ascii_strcasecmp (answer, "0") == 0)
class = 0x10; /* Generic */
else if (ascii_strcasecmp (answer, "1") == 0)
class = 0x11; /* Persona */
else if (ascii_strcasecmp (answer, "2") == 0)
class = 0x12; /* Casual */
else if (ascii_strcasecmp (answer, "3") == 0)
class = 0x13; /* Positive */
else
tty_fprintf (fp, _("Invalid selection.\n"));
xfree (answer);
}
}
if (trust && !quick)
trustsig_prompt (&trust_value, &trust_depth, &trust_regexp);
}
if (!quick)
{
p = get_user_id_native (ctrl, sk_keyid);
tty_fprintf (fp,
_("Are you sure that you want to sign this key with your\n"
"key \"%s\" (%s)\n"), p, keystr_from_pk (pk));
xfree (p);
}
if (selfsig)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("This will be a self-signature.\n"));
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-revocable.\n"));
}
}
else
{
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-revocable.\n"));
}
switch (class)
{
case 0x11:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have not checked this key at all.\n"));
break;
case 0x12:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key casually.\n"));
break;
case 0x13:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key very carefully.\n"));
break;
}
}
tty_fprintf (fp, "\n");
if (opt.batch && opt.answer_yes)
;
else if (quick)
;
else if (!cpr_get_answer_is_yes ("sign_uid.okay",
_("Really sign? (y/N) ")))
continue;
/* Now we can sign the user ids. */
reloop: /* (Must use this, because we are modifying the list.) */
primary_pk = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
primary_pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID
&& (node->flag & NODFLG_MARK_A))
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
log_assert (primary_pk);
memset (&attrib, 0, sizeof attrib);
attrib.non_exportable = local;
attrib.non_revocable = nonrevocable;
attrib.trust_depth = trust_depth;
attrib.trust_value = trust_value;
attrib.trust_regexp = trust_regexp;
node->flag &= ~NODFLG_MARK_A;
/* We force creation of a v4 signature for local
* signatures, otherwise we would not generate the
* subpacket with v3 keys and the signature becomes
* exportable. */
if (selfsig)
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
0x13,
0, 0,
keygen_add_std_prefs, primary_pk,
NULL);
else
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
class,
timestamp, duration,
sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto leave;
}
*ret_modified = 1; /* We changed the keyblock. */
update_trust = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE);
goto reloop;
}
}
/* Delete any sigs that got promoted */
for (node = keyblock; node; node = node->next)
if (node->flag & NODFLG_DELSIG)
delete_kbnode (node);
} /* End loop over signators. */
leave:
release_sk_list (sk_list);
return rc;
}
/*
* Change the passphrase of the primary and all secondary keys. Note
* that it is common to use only one passphrase for the primary and
* all subkeys. However, this is now (since GnuPG 2.1) all up to the
* gpg-agent. Returns 0 on success or an error code.
*/
static gpg_error_t
change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
kbnode_t node;
PKT_public_key *pk;
int any;
u32 keyid[2], subid[2];
char *hexgrip = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key missing!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* Check whether it is likely that we will be able to change the
passphrase for any subkey. */
for (any = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *serialno;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
if (!err && serialno)
; /* Key on card. */
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Maybe stub key. */
else if (!err)
any = 1; /* Key is known. */
else
log_error ("key %s: error getting keyinfo from agent: %s\n",
keystr_with_sub (keyid, subid), gpg_strerror (err));
xfree (serialno);
}
}
err = 0;
if (!any)
{
tty_printf (_("Key has only stub or on-card key items - "
"no passphrase to change.\n"));
goto leave;
}
/* Change the passphrase for all keys. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *desc;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
/* Note that when using --dry-run we don't change the
* passphrase but merely verify the current passphrase. */
desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, !!opt.dry_run,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (err)
log_log ((gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("key %s: error changing passphrase: %s\n"),
keystr_with_sub (keyid, subid),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break;
}
}
leave:
xfree (hexgrip);
xfree (cache_nonce);
xfree (passwd_nonce);
return err;
}
/* Fix various problems in the keyblock. Returns true if the keyblock
was changed. Note that a pointer to the keyblock must be given and
the function may change it (i.e. replacing the first node). */
static int
fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp)
{
int changed = 0;
if (collapse_uids (keyblockp))
changed++;
if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1))
changed++;
reorder_keyblock (*keyblockp);
/* If we modified the keyblock, make sure the flags are right. */
if (changed)
merge_keys_and_selfsig (ctrl, *keyblockp);
return changed;
}
static int
parse_sign_type (const char *str, int *localsig, int *nonrevokesig,
int *trustsig)
{
const char *p = str;
while (*p)
{
if (ascii_strncasecmp (p, "l", 1) == 0)
{
*localsig = 1;
p++;
}
else if (ascii_strncasecmp (p, "nr", 2) == 0)
{
*nonrevokesig = 1;
p += 2;
}
else if (ascii_strncasecmp (p, "t", 1) == 0)
{
*trustsig = 1;
p++;
}
else
return 0;
}
return 1;
}
/*
* Menu driven key editor. If seckey_check is true, then a secret key
* that matches username will be looked for. If it is false, not all
* commands will be available.
*
* Note: to keep track of certain selections we use node->mark MARKBIT_xxxx.
*/
/* Need an SK for this command */
#define KEYEDIT_NEED_SK 1
/* Need an SUB KEY for this command */
#define KEYEDIT_NEED_SUBSK 2
/* Match the tail of the string */
#define KEYEDIT_TAIL_MATCH 8
enum cmdids
{
cmdNONE = 0,
cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN,
cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG,
cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY,
cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF,
cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN,
#ifndef NO_TRUST_MODELS
cmdENABLEKEY, cmdDISABLEKEY,
#endif /*!NO_TRUST_MODELS*/
cmdSHOWPREF,
cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST,
cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD,
cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP
};
static struct
{
const char *name;
enum cmdids id;
int flags;
const char *desc;
} cmds[] =
{
{ "quit", cmdQUIT, 0, N_("quit this menu")},
{ "q", cmdQUIT, 0, NULL},
{ "save", cmdSAVE, 0, N_("save and quit")},
{ "help", cmdHELP, 0, N_("show this help")},
{ "?", cmdHELP, 0, NULL},
{ "fpr", cmdFPR, 0, N_("show key fingerprint")},
{ "grip", cmdGRIP, 0, N_("show the keygrip")},
{ "list", cmdLIST, 0, N_("list key and user IDs")},
{ "l", cmdLIST, 0, NULL},
{ "uid", cmdSELUID, 0, N_("select user ID N")},
{ "key", cmdSELKEY, 0, N_("select subkey N")},
{ "check", cmdCHECK, 0, N_("check signatures")},
{ "c", cmdCHECK, 0, NULL},
{ "change-usage", cmdCHANGEUSAGE, KEYEDIT_NEED_SK, NULL},
{ "cross-certify", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "backsign", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "sign", cmdSIGN, KEYEDIT_TAIL_MATCH,
N_("sign selected user IDs [* see below for related commands]")},
{ "s", cmdSIGN, 0, NULL},
/* "lsign" and friends will never match since "sign" comes first
and it is a tail match. They are just here so they show up in
the help menu. */
{ "lsign", cmdNOP, 0, N_("sign selected user IDs locally")},
{ "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")},
{ "nrsign", cmdNOP, 0,
N_("sign selected user IDs with a non-revocable signature")},
{ "debug", cmdDEBUG, 0, NULL},
{ "adduid", cmdADDUID, KEYEDIT_NEED_SK, N_("add a user ID")},
{ "addphoto", cmdADDPHOTO, KEYEDIT_NEED_SK,
N_("add a photo ID")},
{ "deluid", cmdDELUID, 0, N_("delete selected user IDs")},
/* delphoto is really deluid in disguise */
{ "delphoto", cmdDELUID, 0, NULL},
{ "addkey", cmdADDKEY, KEYEDIT_NEED_SK, N_("add a subkey")},
#ifdef ENABLE_CARD_SUPPORT
{ "addcardkey", cmdADDCARDKEY, KEYEDIT_NEED_SK,
N_("add a key to a smartcard")},
{ "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a key to a smartcard")},
{ "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a backup key to a smartcard")},
#endif /*ENABLE_CARD_SUPPORT */
{ "delkey", cmdDELKEY, 0, N_("delete selected subkeys")},
{ "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK,
N_("add a revocation key")},
{ "delsig", cmdDELSIG, 0,
N_("delete signatures from the selected user IDs")},
{ "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the expiration date for the key or selected subkeys")},
{ "primary", cmdPRIMARY, KEYEDIT_NEED_SK,
N_("flag the selected user ID as primary")},
{ "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */
{ "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL},
{ "pref", cmdPREF, 0, N_("list preferences (expert)")},
{ "showpref", cmdSHOWPREF, 0, N_("list preferences (verbose)")},
{ "setpref", cmdSETPREF, KEYEDIT_NEED_SK,
N_("set preference list for the selected user IDs")},
{ "updpref", cmdSETPREF, KEYEDIT_NEED_SK, NULL},
{ "keyserver", cmdPREFKS, KEYEDIT_NEED_SK,
N_("set the preferred keyserver URL for the selected user IDs")},
{ "notation", cmdNOTATION, KEYEDIT_NEED_SK,
N_("set a notation for the selected user IDs")},
{ "passwd", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the passphrase")},
{ "password", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, NULL},
#ifndef NO_TRUST_MODELS
{ "trust", cmdTRUST, 0, N_("change the ownertrust")},
#endif /*!NO_TRUST_MODELS*/
{ "revsig", cmdREVSIG, 0,
N_("revoke signatures on the selected user IDs")},
{ "revuid", cmdREVUID, KEYEDIT_NEED_SK,
N_("revoke selected user IDs")},
{ "revphoto", cmdREVUID, KEYEDIT_NEED_SK, NULL},
{ "revkey", cmdREVKEY, KEYEDIT_NEED_SK,
N_("revoke key or selected subkeys")},
#ifndef NO_TRUST_MODELS
{ "enable", cmdENABLEKEY, 0, N_("enable key")},
{ "disable", cmdDISABLEKEY, 0, N_("disable key")},
#endif /*!NO_TRUST_MODELS*/
{ "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")},
{ "clean", cmdCLEAN, 0,
N_("compact unusable user IDs and remove unusable signatures from key")},
{ "minimize", cmdMINIMIZE, 0,
N_("compact unusable user IDs and remove all signatures from key")},
{ NULL, cmdNONE, 0, NULL}
};
#ifdef HAVE_LIBREADLINE
/*
These two functions are used by readline for command completion.
*/
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text */
if (cmds[list_index++].desc && strncmp (name, text, len) == 0)
return strdup (name);
}
return NULL;
}
static char **
keyedit_completion (const char *text, int start, int end)
{
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
(void) end;
if (start == 0)
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /* HAVE_LIBREADLINE */
/* Main function of the menu driven key editor. */
void
keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check)
{
enum cmdids cmd = 0;
gpg_error_t err = 0;
KBNODE keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int have_seckey = 0;
int have_anyseckey = 0;
char *answer = NULL;
int redisplay = 1;
int modified = 0;
int sec_shadowing = 0;
int run_subkey_warnings = 0;
int have_commands = !!commands;
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error (_("can't do this in batch mode\n"));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* Due to Windows peculiarities we need to make sure that the
trustdb stale check is done before we open another file
(i.e. by searching for a key). In theory we could make sure
that the files are closed after use but the open/close caches
inhibits that and flushing the cache right before the stale
check is not easy to implement. Thus we take the easy way out
and run the stale check as early as possible. Note, that for
non- W32 platforms it is run indirectly trough a call to
get_validity (). */
check_trustdb_stale (ctrl);
#endif
/* Get the public key */
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, NULL, username, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
goto leave;
}
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* See whether we have a matching secret key. */
if (seckey_check)
{
have_anyseckey = !agent_probe_any_secret_key (ctrl, keyblock);
if (have_anyseckey
&& agent_probe_secret_key (ctrl, keyblock->pkt->pkt.public_key))
{
/* The primary key is also available. */
have_seckey = 1;
}
if (have_seckey && !quiet)
tty_printf (_("Secret key is available.\n"));
else if (have_anyseckey && !quiet)
tty_printf (_("Secret subkeys are available.\n"));
}
/* Main command loop. */
for (;;)
{
int i, arg_number, photo;
const char *arg_string = "";
char *p;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
tty_printf ("\n");
if (redisplay && !quiet)
{
/* Show using flags: with_revoker, with_subkeys. */
show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0);
tty_printf ("\n");
redisplay = 0;
}
if (run_subkey_warnings)
{
run_subkey_warnings = 0;
if (!count_selected_keys (keyblock))
subkey_expire_warning (keyblock);
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
#ifdef HAVE_LIBREADLINE
tty_enable_completion (keyedit_completion);
#endif
answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> ");
cpr_kill_prompt ();
tty_disable_completion ();
}
trim_spaces (answer);
}
while (*answer == '#');
arg_number = 0; /* Here is the init which egcc complains about. */
photo = 0; /* Same here. */
if (!*answer)
cmd = cmdLIST;
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else if (digitp (answer))
{
cmd = cmdSELUID;
arg_number = atoi (answer);
}
else
{
if ((p = strchr (answer, ' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi (p);
arg_string = p;
}
for (i = 0; cmds[i].name; i++)
{
if (cmds[i].flags & KEYEDIT_TAIL_MATCH)
{
size_t l = strlen (cmds[i].name);
size_t a = strlen (answer);
if (a >= l)
{
if (!ascii_strcasecmp (&answer[a - l], cmds[i].name))
{
answer[a - l] = '\0';
break;
}
}
}
else if (!ascii_strcasecmp (answer, cmds[i].name))
break;
}
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
|| ((cmds[i].flags & KEYEDIT_NEED_SUBSK) && have_anyseckey)))
{
tty_printf (_("Need the secret key to do this.\n"));
cmd = cmdNOP;
}
else
cmd = cmds[i].id;
}
/* Dispatch the command. */
switch (cmd)
{
case cmdHELP:
for (i = 0; cmds[i].name; i++)
{
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
||((cmds[i].flags&KEYEDIT_NEED_SUBSK)&&have_anyseckey)))
; /* Skip those item if we do not have the secret key. */
else if (cmds[i].desc)
tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc));
}
tty_printf ("\n");
tty_printf
(_("* The 'sign' command may be prefixed with an 'l' for local "
"signatures (lsign),\n"
" a 't' for trust signatures (tsign), an 'nr' for "
"non-revocable signatures\n"
" (nrsign), or any combination thereof (ltsign, "
"tnrsign, etc.).\n"));
break;
case cmdLIST:
redisplay = 1;
break;
case cmdFPR:
show_key_and_fingerprint
(ctrl,
keyblock, (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1))));
break;
case cmdGRIP:
show_key_and_grip (keyblock);
break;
case cmdSELUID:
if (strlen (arg_string) == NAMEHASH_LEN * 2)
redisplay = menu_select_uid_namehash (keyblock, arg_string);
else
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
redisplay = menu_select_uid (keyblock, arg_number);
}
break;
case cmdSELKEY:
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
if (menu_select_key (keyblock, arg_number, p))
redisplay = 1;
}
break;
case cmdCHECK:
if (key_check_all_keysigs (ctrl, -1, keyblock,
count_selected_uids (keyblock),
!strcmp (arg_string, "selfsig")))
modified = 1;
break;
case cmdSIGN:
{
int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0;
if (pk->flags.revoked)
{
tty_printf (_("Key is revoked."));
if (opt.expert)
{
tty_printf (" ");
if (!cpr_get_answer_is_yes
("keyedit.sign_revoked.okay",
_("Are you sure you still want to sign it? (y/N) ")))
break;
}
else
{
tty_printf (_(" Unable to sign.\n"));
break;
}
}
if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock))
{
int result;
if (opt.only_sign_text_ids)
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all text user IDs? (y/N) "));
else
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all user IDs? (y/N) "));
if (! result)
{
if (opt.interactive)
interactive = 1;
else
{
tty_printf (_("Hint: Select the user IDs to sign\n"));
have_commands = 0;
break;
}
}
}
/* What sort of signing are we doing? */
if (!parse_sign_type
(answer, &localsig, &nonrevokesig, &trustsig))
{
tty_printf (_("Unknown signature type '%s'\n"), answer);
break;
}
sign_uids (ctrl, NULL, keyblock, locusr, &modified,
localsig, nonrevokesig, trustsig, interactive, 0);
}
break;
case cmdDEBUG:
dump_kbnode (keyblock);
break;
case cmdTOGGLE:
/* The toggle command is a leftover from old gpg versions
where we worked with a secret and a public keyring. It
is not necessary anymore but we keep this command for the
sake of scripts using it. */
redisplay = 1;
break;
case cmdADDPHOTO:
if (RFC2440)
{
tty_printf (_("This command is not allowed while in %s mode.\n"),
gnupg_compliance_option_string (opt.compliance));
break;
}
photo = 1;
/* fall through */
case cmdADDUID:
if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL))
{
update_trust = 1;
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdDELUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (real_uids_left (keyblock) < 1)
tty_printf (_("You can't delete the last user ID!\n"));
else if (cpr_get_answer_is_yes
("keyedit.remove.uid.okay",
n1 > 1 ? _("Really remove all selected user IDs? (y/N) ")
: _("Really remove this user ID? (y/N) ")))
{
menu_deluid (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdDELSIG:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (menu_delsig (ctrl, keyblock))
{
/* No redisplay here, because it may scroll away some
* of the status output of this command. */
modified = 1;
}
}
break;
case cmdADDKEY:
if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
#ifdef ENABLE_CARD_SUPPORT
case cmdADDCARDKEY:
if (!card_generate_subkey (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdKEYTOCARD:
{
KBNODE node = NULL;
switch (count_selected_keys (keyblock))
{
case 0:
if (cpr_get_answer_is_yes
("keyedit.keytocard.use_primary",
/* TRANSLATORS: Please take care: This is about
moving the key and not about removing it. */
_("Really move the primary key? (y/N) ")))
node = keyblock;
break;
case 1:
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& node->flag & NODFLG_SELKEY)
break;
}
break;
default:
tty_printf (_("You must select exactly one key.\n"));
break;
}
if (node)
{
PKT_public_key *xxpk = node->pkt->pkt.public_key;
if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0))
{
redisplay = 1;
sec_shadowing = 1;
}
}
}
break;
case cmdBKUPTOCARD:
{
/* Ask for a filename, check whether this is really a
backup key as generated by the card generation, parse
that key and store it on card. */
KBNODE node;
char *fname;
PACKET *pkt;
IOBUF a;
struct parse_packet_ctx_s parsectx;
if (!*arg_string)
{
tty_printf (_("Command expects a filename argument\n"));
break;
}
if (*arg_string == DIRSEP_C)
fname = xstrdup (arg_string);
else if (*arg_string == '~')
fname = make_filename (arg_string, NULL);
else
fname = make_filename (gnupg_homedir (), arg_string, NULL);
/* Open that file. */
a = iobuf_open (fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if (!a)
{
tty_printf (_("Can't open '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
break;
}
/* Parse and check that file. */
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
err = parse_packet (&parsectx, pkt);
deinit_parse_packet (&parsectx);
iobuf_close (a);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname);
if (!err && pkt->pkttype != PKT_SECRET_KEY
&& pkt->pkttype != PKT_SECRET_SUBKEY)
err = GPG_ERR_NO_SECKEY;
if (err)
{
tty_printf (_("Error reading backup key from '%s': %s\n"),
fname, gpg_strerror (err));
xfree (fname);
free_packet (pkt, NULL);
xfree (pkt);
break;
}
xfree (fname);
node = new_kbnode (pkt);
/* Transfer it to gpg-agent which handles secret keys. */
err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0);
/* Treat the pkt as a public key. */
pkt->pkttype = PKT_PUBLIC_KEY;
/* Ask gpg-agent to store the secret key to card. */
if (card_store_subkey (node, 0))
{
redisplay = 1;
sec_shadowing = 1;
}
release_kbnode (node);
}
break;
#endif /* ENABLE_CARD_SUPPORT */
case cmdDELKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
tty_printf (_("You must select at least one key.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "key");
}
else if (!cpr_get_answer_is_yes
("keyedit.remove.subkey.okay",
n1 > 1 ? _("Do you really want to delete the "
"selected keys? (y/N) ")
: _("Do you really want to delete this key? (y/N) ")))
;
else
{
menu_delkey (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdADDREVOKER:
{
int sensitive = 0;
if (ascii_strcasecmp (arg_string, "sensitive") == 0)
sensitive = 1;
if (menu_addrevoker (ctrl, keyblock, sensitive))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
}
break;
case cmdREVUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (cpr_get_answer_is_yes
("keyedit.revoke.uid.okay",
n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ")
: _("Really revoke this user ID? (y/N) ")))
{
if (menu_revuid (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdREVKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
_("Do you really want to revoke"
" the entire key? (y/N) ")))
{
if (menu_revkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
}
else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
n1 > 1 ?
_("Do you really want to revoke"
" the selected subkeys? (y/N) ")
: _("Do you really want to revoke"
" this subkey? (y/N) ")))
{
if (menu_revsubkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
if (modified)
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdEXPIRE:
if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE)
{
merge_keys_and_selfsig (ctrl, keyblock);
run_subkey_warnings = 1;
modified = 1;
redisplay = 1;
}
break;
case cmdCHANGEUSAGE:
if (menu_changeusage (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdBACKSIGN:
if (menu_backsign (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
break;
case cmdPRIMARY:
if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdPASSWD:
change_passphrase (ctrl, keyblock);
break;
#ifndef NO_TRUST_MODELS
case cmdTRUST:
if (opt.trust_model == TM_EXTERNAL)
{
tty_printf (_("Owner trust may not be set while "
"using a user provided trust database\n"));
break;
}
show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0);
tty_printf ("\n");
if (edit_ownertrust (ctrl, find_kbnode (keyblock,
PKT_PUBLIC_KEY)->pkt->pkt.
public_key, 1))
{
redisplay = 1;
/* No real need to set update_trust here as
edit_ownertrust() calls revalidation_mark()
anyway. */
update_trust = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 1);
}
break;
case cmdSHOWPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 2);
}
break;
case cmdSETPREF:
{
PKT_user_id *tempuid;
keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0);
tempuid = keygen_get_std_prefs ();
tty_printf (_("Set preference list to:\n"));
show_prefs (tempuid, NULL, 1);
free_user_id (tempuid);
if (cpr_get_answer_is_yes
("keyedit.setpref.okay",
count_selected_uids (keyblock) ?
_("Really update the preferences"
" for the selected user IDs? (y/N) ")
: _("Really update the preferences? (y/N) ")))
{
if (menu_set_preferences (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdPREFKS:
if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOTATION:
if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOP:
break;
case cmdREVSIG:
if (menu_revsig (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
}
break;
#ifndef NO_TRUST_MODELS
case cmdENABLEKEY:
case cmdDISABLEKEY:
if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY))
{
redisplay = 1;
modified = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdSHOWPHOTO:
menu_showphoto (ctrl, keyblock);
break;
case cmdCLEAN:
if (menu_clean (ctrl, keyblock, 0))
redisplay = modified = 1;
break;
case cmdMINIMIZE:
if (menu_clean (ctrl, keyblock, 1))
redisplay = modified = 1;
break;
case cmdQUIT:
if (have_commands)
goto leave;
if (!modified && !sec_shadowing)
goto leave;
if (!cpr_get_answer_is_yes ("keyedit.save.okay",
_("Save changes? (y/N) ")))
{
if (cpr_enabled ()
|| cpr_get_answer_is_yes ("keyedit.cancel.okay",
_("Quit without saving? (y/N) ")))
goto leave;
break;
}
/* fall through */
case cmdSAVE:
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (sec_shadowing)
{
err = agent_scd_learn (NULL, 1);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (!modified && !sec_shadowing)
tty_printf (_("Key not changed so no update needed.\n"));
if (update_trust)
{
revalidation_mark (ctrl);
update_trust = 0;
}
goto leave;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
}
} /* End of the main command loop. */
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
xfree (answer);
}
/* Change the passphrase of the secret key identified by USERNAME. */
void
keyedit_passwd (ctrl_t ctrl, const char *username)
{
gpg_error_t err;
PKT_public_key *pk;
kbnode_t keyblock = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock);
if (err)
goto leave;
err = change_passphrase (ctrl, keyblock);
leave:
release_kbnode (keyblock);
free_public_key (pk);
if (err)
{
log_info ("error changing the passphrase for '%s': %s\n",
username, gpg_strerror (err));
write_status_error ("keyedit.passwd", err);
}
else
write_status_text (STATUS_SUCCESS, "keyedit.passwd");
}
/* Helper for quick commands to find the keyblock for USERNAME.
* Returns on success the key database handle at R_KDBHD and the
* keyblock at R_KEYBLOCK. */
static gpg_error_t
quick_find_keyblock (ctrl_t ctrl, const char *username,
KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
kbnode_t node;
*r_kdbhd = NULL;
*r_keyblock = NULL;
/* Search the key; we don't want the whole getkey stuff here. */
kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
err = gpg_error_from_syserror ();
goto leave;
}
err = classify_user_id (username, &desc, 1);
if (!err)
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
{
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
/* Now with the keyblock retrieved, search again to detect an
ambiguous specification. We need to save the found state so
that we can do an update later. */
keydb_push_found_state (kdbhd);
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
keydb_pop_found_state (kdbhd);
if (!err)
{
/* We require the secret primary key to set the primary UID. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
log_assert (node);
if (!agent_probe_secret_key (ctrl, node->pkt->pkt.public_key))
err = gpg_error (GPG_ERR_NO_SECKEY);
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_PUBKEY);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"),
username, gpg_strerror (err));
goto leave;
}
fix_keyblock (ctrl, &keyblock);
merge_keys_and_selfsig (ctrl, keyblock);
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended adding of a new keyid. USERNAME specifies the
key. NEWUID is the new user id to add to the key. */
void
keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
char *uidstring = NULL;
uidstring = xstrdup (newuid);
trim_spaces (uidstring);
if (!*uidstring)
{
log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
leave:
xfree (uidstring);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended revocation of a keyid. USERNAME specifies the
key. UIDTOREV is the user id revoke from the key. */
void
keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t node;
int modified = 0;
size_t revlen;
size_t valid_uids;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
/* Too make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = keyblock; node; node = node->next)
valid_uids += (node->pkt->pkttype == PKT_USER_ID
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired);
/* Find the right UID. */
revlen = strlen (uidtorev);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& revlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen))
{
struct revocation_reason_info *reason;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("cannot revoke the last valid user ID.\n"));
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
reason = get_default_uid_revocation_reason ();
err = core_revuid (ctrl, keyblock, node, reason, &modified);
release_revocation_reason_info (reason);
if (err)
goto leave;
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
goto leave;
}
}
err = gpg_error (GPG_ERR_NO_USER_ID);
leave:
if (err)
{
log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err));
write_status_error ("keyedit.revoke.uid", err);
}
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended setting of the primary uid. USERNAME specifies the key.
PRIMARYUID is the user id which shall be primary. */
void
keyedit_quick_set_primary (ctrl_t ctrl, const char *username,
const char *primaryuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t primaryuidlen;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
err = quick_find_keyblock (ctrl, username, &kdbhd, &keyblock);
if (err)
goto leave;
/* Find and mark the UID - we mark only the first valid one. */
primaryuidlen = strlen (primaryuid);
any = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& !any
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired
&& primaryuidlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, primaryuid, primaryuidlen))
{
node->flag |= NODFLG_SELUID;
any = 1;
}
else
node->flag &= ~NODFLG_SELUID;
}
if (!any)
err = gpg_error (GPG_ERR_NO_USER_ID);
else if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
}
else
err = gpg_error (GPG_ERR_GENERAL);
if (err)
log_error (_("setting the primary user ID failed: %s\n"),
gpg_strerror (err));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Find a keyblock by fingerprint because only this uniquely
* identifies a key and may thus be used to select a key for
* unattended subkey creation os key signing. */
static gpg_error_t
find_by_primary_fpr (ctrl_t ctrl, const char *fpr,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
*r_keyblock = NULL;
*r_kdbhd = NULL;
if (classify_user_id (fpr, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, NULL, fpr, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err));
goto leave;
}
/* Check that the primary fingerprint has been given. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
if (desc.mode == KEYDB_SEARCH_MODE_FPR
&& fprlen == desc.fprlen
&& !memcmp (fprbin, desc.u.fpr, fprlen))
;
else
{
log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
err = 0;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended key signing function. If the key specifified by FPR is
available and FPR is the primary fingerprint all user ids of the
key are signed using the default signing key. If UIDS is an empty
list all usable UIDs are signed, if it is not empty, only those
user ids matching one of the entries of the list are signed. With
LOCAL being true the signatures are marked as non-exportable. */
void
keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
strlist_t locusr, int local)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int modified = 0;
PKT_public_key *pk;
kbnode_t node;
strlist_t sl;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
key and may thus be used to select a key for unattended key
signing. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* Give some info in verbose. */
if (opt.verbose)
{
show_key_with_all_names (ctrl, es_stdout, keyblock, 0,
1/*with_revoker*/, 1/*with_fingerprint*/,
0, 0, 1);
es_fflush (es_stdout);
}
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n"));
goto leave;
}
/* Set the flags according to the UIDS list. Fixme: We may want to
use classify_user_id along with dedicated compare functions so
that we match the same way as in the key lookup. */
any = 0;
menu_select_uid (keyblock, 0); /* Better clear the flags first. */
for (sl=uids; sl; sl = sl->next)
{
const char *name = sl->d;
int count = 0;
sl->flags &= ~(1|2); /* Clear flags used for error reporting. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->attrib_data)
;
else if (*name == '='
&& strlen (name+1) == uid->len
&& !memcmp (uid->name, name + 1, uid->len))
{ /* Exact match - we don't do a check for ambiguity
* in this case. */
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
}
else if (ascii_memistr (uid->name, uid->len,
*name == '*'? name+1:name))
{
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
count++;
}
}
}
if (count > 1)
{
any = -1; /* Force failure at end. */
sl->flags |= 2; /* Report as ambiguous. */
}
}
/* Check whether all given user ids were found. */
for (sl=uids; sl; sl = sl->next)
if (!(sl->flags & 1))
any = -1; /* That user id was not found. */
/* Print an error if there was a problem with the user ids. */
if (uids && any < 1)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
es_fflush (es_stdout);
for (sl=uids; sl; sl = sl->next)
{
if ((sl->flags & 2))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME));
else if (!(sl->flags & 1))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_NOT_FOUND));
}
log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n"));
goto leave;
}
/* Sign. */
sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1);
es_fflush (es_stdout);
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
if (update_trust)
revalidation_mark (ctrl);
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended subkey creation function.
*
*/
void
keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended subkey
* creation. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
goto leave;
}
/* Create the subkey. Note that the called function already prints
* an error message. */
if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr))
modified = 1;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended expiration setting function for the main key. If
* SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is
* expected to be an array of fingerprints for subkeys to change. It
* may also be an array which just one item "*" to indicate that all
* keys shall be set to that expiration date.
*/
void
keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr,
char **subkeyfprs)
{
gpg_error_t err;
kbnode_t keyblock, node;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
u32 expire;
int primary_only = 0;
int idx;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended
* expiration setting. */
err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
if (err)
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
log_error (_("'%s' is not a valid expiration time\n"), expirestr);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (expire)
expire += make_timestamp ();
/* Check whether a subkey's expiration time shall be changed or the
* expiration time of all keys. */
if (!subkeyfprs || !subkeyfprs[0])
primary_only = 1;
else if ( !strcmp (subkeyfprs[0], "*") && !subkeyfprs[1])
{
/* Change all subkeys keys which have not been revoked and are
* not yet expired. */
merge_keys_and_selfsig (ctrl, keyblock);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked
&& !pk->has_expired)
node->flag |= NODFLG_SELKEY;
}
}
else
{
/* Change specified subkeys. */
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
err = 0;
merge_keys_and_selfsig (ctrl, keyblock);
for (idx=0; subkeyfprs[idx]; idx++)
{
int any = 0;
/* Parse the fingerprint. */
if (classify_user_id (subkeyfprs[idx], &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a proper fingerprint\n"),
subkeyfprs[idx] );
if (!err)
err = gpg_error (GPG_ERR_INV_NAME);
continue;
}
/* Set the flag for the matching non revoked subkey. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked )
{
fingerprint_from_pk (pk, fprbin, &fprlen);
if (fprlen == 20 && !memcmp (fprbin, desc.u.fpr, 20))
{
node->flag |= NODFLG_SELKEY;
any = 1;
}
}
}
if (!any)
{
log_error (_("subkey \"%s\" not found\n"), subkeyfprs[idx]);
if (!err)
err = gpg_error (GPG_ERR_NOT_FOUND);
}
}
if (err)
goto leave;
}
/* Set the new expiration date. */
err = menu_expire (ctrl, keyblock, primary_only? 1 : 2, expire);
if (gpg_err_code (err) == GPG_ERR_TRUE)
modified = 1;
else if (err)
goto leave;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
if (err)
write_status_error ("set_expire", err);
}
static void
tty_print_notations (int indent, PKT_signature * sig)
{
int first = 1;
struct notation *notation, *nd;
if (indent < 0)
{
first = 0;
indent = -indent;
}
notation = sig_to_notation (sig);
for (nd = notation; nd; nd = nd->next)
{
if (!first)
tty_printf ("%*s", indent, "");
else
first = 0;
tty_print_utf8_string (nd->name, strlen (nd->name));
tty_printf ("=");
tty_print_utf8_string (nd->value, strlen (nd->value));
tty_printf ("\n");
}
free_notation (notation);
}
/*
* Show preferences of a public keyblock.
*/
static void
show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose)
{
const prefitem_t fake = { 0, 0 };
const prefitem_t *prefs;
int i;
if (!uid)
return;
if (uid->prefs)
prefs = uid->prefs;
else if (verbose)
prefs = &fake;
else
return;
if (verbose)
{
int any, des_seen = 0, sha1_seen = 0, uncomp_seen = 0;
tty_printf (" ");
tty_printf (_("Cipher: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_SYM)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!openpgp_cipher_test_algo (prefs[i].value)
&& prefs[i].value < 100)
tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == CIPHER_ALGO_3DES)
des_seen = 1;
}
}
if (!des_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", openpgp_cipher_algo_name (CIPHER_ALGO_3DES));
}
tty_printf ("\n ");
tty_printf (_("AEAD: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_AEAD)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!openpgp_aead_test_algo (prefs[i].value)
&& prefs[i].value < 100)
tty_printf ("%s", openpgp_aead_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
}
}
tty_printf ("\n ");
tty_printf (_("Digest: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_HASH)
{
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100)
tty_printf ("%s", gcry_md_algo_name (prefs[i].value));
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == DIGEST_ALGO_SHA1)
sha1_seen = 1;
}
}
if (!sha1_seen)
{
if (any)
tty_printf (", ");
tty_printf ("%s", gcry_md_algo_name (DIGEST_ALGO_SHA1));
}
tty_printf ("\n ");
tty_printf (_("Compression: "));
for (i = any = 0; prefs[i].type; i++)
{
if (prefs[i].type == PREFTYPE_ZIP)
{
const char *s = compress_algo_to_string (prefs[i].value);
if (any)
tty_printf (", ");
any = 1;
/* We don't want to display strings for experimental algos */
if (s && prefs[i].value < 100)
tty_printf ("%s", s);
else
tty_printf ("[%d]", prefs[i].value);
if (prefs[i].value == COMPRESS_ALGO_NONE)
uncomp_seen = 1;
}
}
if (!uncomp_seen)
{
if (any)
tty_printf (", ");
else
{
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_ZIP));
tty_printf (", ");
}
tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_NONE));
}
if (uid->flags.mdc || uid->flags.aead || !uid->flags.ks_modify)
{
tty_printf ("\n ");
tty_printf (_("Features: "));
any = 0;
if (uid->flags.mdc)
{
tty_printf ("MDC");
any = 1;
}
if (!uid->flags.aead)
{
if (any)
tty_printf (", ");
tty_printf ("AEAD");
}
if (!uid->flags.ks_modify)
{
if (any)
tty_printf (", ");
tty_printf (_("Keyserver no-modify"));
}
}
tty_printf ("\n");
if (selfsig)
{
const byte *pref_ks;
size_t pref_ks_len;
pref_ks = parse_sig_subpkt (selfsig, 1,
SIGSUBPKT_PREF_KS, &pref_ks_len);
if (pref_ks && pref_ks_len)
{
tty_printf (" ");
tty_printf (_("Preferred keyserver: "));
tty_print_utf8_string (pref_ks, pref_ks_len);
tty_printf ("\n");
}
if (selfsig->flags.notation)
{
tty_printf (" ");
tty_printf (_("Notations: "));
tty_print_notations (5 + strlen (_("Notations: ")), selfsig);
}
}
}
else
{
tty_printf (" ");
for (i = 0; prefs[i].type; i++)
{
tty_printf (" %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' :
prefs[i].type == PREFTYPE_AEAD ? 'A' :
prefs[i].type == PREFTYPE_HASH ? 'H' :
prefs[i].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[i].value);
}
if (uid->flags.mdc)
tty_printf (" [mdc]");
if (uid->flags.aead)
tty_printf (" [aead]");
if (!uid->flags.ks_modify)
tty_printf (" [no-ks-modify]");
tty_printf ("\n");
}
}
/* This is the version of show_key_with_all_names used when
opt.with_colons is used. It prints all available data in a easy to
parse format and does not translate utf8 */
static void
show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
{
KBNODE node;
int i, j, ulti_hack = 0;
byte pk_version = 0;
PKT_public_key *primary = NULL;
int have_seckey;
if (!fp)
fp = es_stdout;
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
u32 keyid[2];
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk_version = pk->version;
primary = pk;
}
keyid_from_pk (pk, keyid);
have_seckey = agent_probe_secret_key (ctrl, pk);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
es_fputs (have_seckey? "sec:" : "pub:", fp);
else
es_fputs (have_seckey? "ssb:" : "sub:", fp);
if (!pk->flags.valid)
es_putc ('i', fp);
else if (pk->flags.revoked)
es_putc ('r', fp);
else if (pk->has_expired)
es_putc ('e', fp);
else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
{
int trust = get_validity_info (ctrl, keyblock, pk, NULL);
if (trust == 'u')
ulti_hack = 1;
es_putc (trust, fp);
}
es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::",
nbits_from_pk (pk),
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
(ulong) pk->timestamp, (ulong) pk->expiredate);
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& !(opt.fast_list_mode || opt.no_expensive_trust_checks))
es_putc (get_ownertrust_info (ctrl, pk, 0), fp);
es_putc (':', fp);
es_putc (':', fp);
es_putc (':', fp);
/* Print capabilities. */
if ((pk->pubkey_usage & PUBKEY_USAGE_ENC))
es_putc ('e', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_SIG))
es_putc ('s', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_CERT))
es_putc ('c', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
es_putc ('a', fp);
es_putc ('\n', fp);
print_fingerprint (ctrl, fp, pk, 0);
print_revokers (fp, pk);
}
}
/* the user ids */
i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (uid->attrib_data)
es_fputs ("uat:", fp);
else
es_fputs ("uid:", fp);
if (uid->flags.revoked)
es_fputs ("r::::::::", fp);
else if (uid->flags.expired)
es_fputs ("e::::::::", fp);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
es_fputs ("::::::::", fp);
else
{
int uid_validity;
if (primary && !ulti_hack)
uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
else
uid_validity = 'u';
es_fprintf (fp, "%c::::::::", uid_validity);
}
if (uid->attrib_data)
es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (fp, uid->name, uid->len, ":", NULL);
es_putc (':', fp);
/* signature class */
es_putc (':', fp);
/* capabilities */
es_putc (':', fp);
/* preferences */
if (pk_version > 3 || uid->selfsigversion > 3)
{
const prefitem_t *prefs = uid->prefs;
for (j = 0; prefs && prefs[j].type; j++)
{
if (j)
es_putc (' ', fp);
es_fprintf (fp,
"%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' :
prefs[j].type == PREFTYPE_HASH ? 'H' :
prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[j].value);
}
if (uid->flags.mdc)
es_fputs (",mdc", fp);
if (uid->flags.aead)
es_fputs (",aead", fp);
if (!uid->flags.ks_modify)
es_fputs (",no-ks-modify", fp);
}
es_putc (':', fp);
/* flags */
es_fprintf (fp, "%d,", i);
if (uid->flags.primary)
es_putc ('p', fp);
if (uid->flags.revoked)
es_putc ('r', fp);
if (uid->flags.expired)
es_putc ('e', fp);
if ((node->flag & NODFLG_SELUID))
es_putc ('s', fp);
if ((node->flag & NODFLG_MARK_A))
es_putc ('m', fp);
es_putc (':', fp);
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
#ifdef USE_TOFU
enum tofu_policy policy;
if (! tofu_get_policy (ctrl, primary, uid, &policy)
&& policy != TOFU_POLICY_NONE)
es_fprintf (fp, "%s", tofu_policy_str (policy));
#endif /*USE_TOFU*/
}
es_putc (':', fp);
es_putc ('\n', fp);
}
}
}
static void
show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk, unsigned int flag,
int with_prefs)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (!flag || (flag && (node->flag & flag)))
{
if (!(flag & NODFLG_MARK_A) && pk)
tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid));
if (flag & NODFLG_MARK_A)
tty_fprintf (fp, " ");
else if (node->flag & NODFLG_SELUID)
tty_fprintf (fp, "(%d)* ", i);
else if (uid->flags.primary)
tty_fprintf (fp, "(%d). ", i);
else
tty_fprintf (fp, "(%d) ", i);
tty_print_utf8_string2 (fp, uid->name, uid->len, 0);
tty_fprintf (fp, "\n");
if (with_prefs && pk)
{
if (pk->version > 3 || uid->selfsigversion > 3)
{
PKT_signature *selfsig = NULL;
KBNODE signode;
for (signode = node->next;
signode && signode->pkt->pkttype == PKT_SIGNATURE;
signode = signode->next)
{
if (signode->pkt->pkt.signature->
flags.chosen_selfsig)
{
selfsig = signode->pkt->pkt.signature;
break;
}
}
show_prefs (uid, selfsig, with_prefs == 2);
}
else
tty_fprintf (fp, _("There are no preferences on a"
" PGP 2.x-style user ID.\n"));
}
}
}
}
}
/*
* Display the key a the user ids, if only_marked is true, do only so
* for user ids with mark A flag set and do not display the index
* number. If FP is not NULL print to the given stream and not to the
* tty (ignored in with-colons mode).
*/
static void
show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked, int with_revoker,
int with_fpr, int with_subkeys, int with_prefs,
int nowarn)
{
gpg_error_t err;
kbnode_t node;
int i;
int do_warn = 0;
int have_seckey = 0;
char *serialno = NULL;
PKT_public_key *primary = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
if (opt.with_colons)
{
show_key_with_all_names_colon (ctrl, fp, keyblock);
return;
}
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !is_deleted_kbnode (node)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *otrust = "err";
const char *trust = "err";
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* do it here, so that debug messages don't clutter the
* output */
static int did_warn = 0;
trust = get_validity_string (ctrl, pk, NULL);
otrust = get_ownertrust_string (ctrl, pk, 0);
/* Show a warning once */
if (!did_warn
&& (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
& TRUST_FLAG_PENDING_CHECK))
{
did_warn = 1;
do_warn = 1;
}
primary = pk;
}
if (pk->flags.revoked)
{
char *user = get_user_id_string_native (ctrl, pk->revoked.keyid);
tty_fprintf (fp,
_("The following key was revoked on"
" %s by %s key %s\n"),
revokestr_from_pk (pk),
gcry_pk_algo_name (pk->revoked.algo), user);
xfree (user);
}
if (with_revoker)
{
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
for (i = 0; i < pk->numrevkeys; i++)
{
u32 r_keyid[2];
char *user;
const char *algo;
algo = gcry_pk_algo_name (pk->revkey[i].algid);
keyid_from_fingerprint (ctrl, pk->revkey[i].fpr,
pk->revkey[i].fprlen, r_keyid);
user = get_user_id_string_native (ctrl, r_keyid);
tty_fprintf (fp,
_("This key may be revoked by %s key %s"),
algo ? algo : "?", user);
if (pk->revkey[i].class & 0x40)
{
tty_fprintf (fp, " ");
tty_fprintf (fp, _("(sensitive)"));
}
tty_fprintf (fp, "\n");
xfree (user);
}
}
keyid_from_pk (pk, NULL);
xfree (serialno);
serialno = NULL;
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n",
gpg_strerror (err));
have_seckey = 0;
}
else
have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
xfree (hexgrip);
}
tty_fprintf
(fp, "%s%c %s/%s",
node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" :
node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" :
have_seckey ? "ssb" :
"sub",
(node->flag & NODFLG_SELKEY) ? '*' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (pk->keyid));
if (opt.legacy_list_mode)
tty_fprintf (fp, " ");
else
tty_fprintf (fp, "\n ");
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
if (pk->flags.revoked)
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
else if (pk->has_expired)
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
else
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1));
tty_fprintf (fp, "\n");
if (serialno)
{
/* The agent told us that a secret key is available and
that it has been stored on a card. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s\n",
4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s\n", serialno);
}
else if (pk->seckey_info
&& pk->seckey_info->is_protected
&& pk->seckey_info->s2k.mode == 1002)
{
/* FIXME: Check whether this code path is still used. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (pk->seckey_info->ivlen == 16
&& !memcmp (pk->seckey_info->iv,
"\xD2\x76\x00\x01\x24\x01", 6))
{
/* This is an OpenPGP card. */
for (i = 8; i < 14; i++)
{
if (i == 10)
tty_fprintf (fp, " ");
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
}
else
{
/* Unknown card: Print all. */
for (i = 0; i < pk->seckey_info->ivlen; i++)
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
if (opt.trust_model != TM_ALWAYS)
{
tty_fprintf (fp, "%*s",
opt.legacy_list_mode?
((int) keystrlen () + 13):5, "");
/* Ownertrust is only meaningful for the PGP or
classic trust models, or PGP combined with TOFU */
if (opt.trust_model == TM_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
int width = 14 - strlen (otrust);
if (width <= 0)
width = 1;
tty_fprintf (fp, _("trust: %s"), otrust);
tty_fprintf (fp, "%*s", width, "");
}
tty_fprintf (fp, _("validity: %s"), trust);
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED))
{
tty_fprintf (fp, "*** ");
tty_fprintf (fp, _("This key has been disabled"));
tty_fprintf (fp, "\n");
}
}
if ((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr)
{
print_fingerprint (ctrl, fp, pk, 2);
tty_fprintf (fp, "\n");
}
}
}
show_names (ctrl, fp,
keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs);
if (do_warn && !nowarn)
tty_fprintf (fp, _("Please note that the shown key validity"
" is not necessarily correct\n"
"unless you restart the program.\n"));
xfree (serialno);
}
/* Display basic key information. This function is suitable to show
* information on the key without any dependencies on the trustdb or
* any other internal GnuPG stuff. KEYBLOCK may either be a public or
* a secret key. This function may be called with KEYBLOCK containing
* secret keys and thus the printing of "pub" vs. "sec" does only
* depend on the packet type and not by checking with gpg-agent. If
- * PRINT_SEC ist set "sec" is printed instead of "pub". */
+ * PRINT_SEC is set "sec" is printed instead of "pub". */
void
show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec)
{
KBNODE node;
int i;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* The primary key */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *tag;
if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec)
tag = "sec";
else
tag = "pub";
/* Note, we use the same format string as in other show
functions to make the translation job easier. */
tty_printf ("%s %s/%s ",
tag,
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk));
tty_printf (_("created: %s"), datestr_from_pk (pk));
tty_printf (" ");
tty_printf (_("expires: %s"), expirestr_from_pk (pk));
tty_printf ("\n");
print_fingerprint (ctrl, NULL, pk, 3);
tty_printf ("\n");
}
}
/* The user IDs. */
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
tty_printf (" ");
if (uid->flags.revoked)
tty_printf ("[%s] ", _("revoked"));
else if (uid->flags.expired)
tty_printf ("[%s] ", _("expired"));
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
}
}
static void
show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk));
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_print_utf8_string (uid->name, uid->len);
break;
}
}
tty_printf ("\n");
if (pk)
print_fingerprint (ctrl, NULL, pk, 2);
if (with_subkeys)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("sub %s/%s %s [%s]\n",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
print_fingerprint (ctrl, NULL, pk, 4);
}
}
}
}
/* Show a listing of the primary and its subkeys along with their
keygrips. */
static void
show_key_and_grip (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
char *hexgrip;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("%s %s/%s %s [%s]\n",
node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
if (!hexkeygrip_from_pk (pk, &hexgrip))
{
tty_printf (" Keygrip: %s\n", hexgrip);
xfree (hexgrip);
}
}
}
}
/* Show a warning if no uids on the key have the primary uid flag
set. */
static void
no_primary_warning (KBNODE keyblock)
{
KBNODE node;
int have_primary = 0, uid_count = 0;
/* TODO: if we ever start behaving differently with a primary or
non-primary attribute ID, we will need to check for attributes
here as well. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data == NULL)
{
uid_count++;
if (node->pkt->pkt.user_id->flags.primary == 2)
{
have_primary = 1;
break;
}
}
}
if (uid_count > 1 && !have_primary)
log_info (_
("WARNING: no user ID has been marked as primary. This command"
" may\n cause a different user ID to become"
" the assumed primary.\n"));
}
/* Print a warning if the latest encryption subkey expires soon. This
function is called after the expire data of the primary key has
been changed. */
static void
subkey_expire_warning (kbnode_t keyblock)
{
u32 curtime = make_timestamp ();
kbnode_t node;
PKT_public_key *pk;
/* u32 mainexpire = 0; */
u32 subexpire = 0;
u32 latest_date = 0;
for (node = keyblock; node; node = node->next)
{
/* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */
/* { */
/* pk = node->pkt->pkt.public_key; */
/* mainexpire = pk->expiredate; */
/* } */
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (!pk->flags.valid)
continue;
if (pk->flags.revoked)
continue;
if (pk->timestamp > curtime)
continue; /* Ignore future keys. */
if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC))
continue; /* Not an encryption key. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
subexpire = pk->expiredate;
}
}
if (!subexpire)
return; /* No valid subkey with an expiration time. */
if (curtime + (10*86400) > subexpire)
{
log_info (_("WARNING: Your encryption subkey expires soon.\n"));
log_info (_("You may want to change its expiration date too.\n"));
}
}
/*
* Ask for a new user id, add the self-signature, and update the
* keyblock. If UIDSTRING is not NULL the user ID is generated
* unattended using that string. UIDSTRING is expected to be utf-8
* encoded and white space trimmed. Returns true if there is a new
* user id.
*/
static int
menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock,
int photo, const char *photo_name, const char *uidstring)
{
PKT_user_id *uid;
PKT_public_key *pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
KBNODE node;
KBNODE pub_where = NULL;
gpg_error_t err;
if (photo && uidstring)
return 0; /* Not allowed. */
for (node = pub_keyblock; node; pub_where = node, node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break;
}
if (!node) /* No subkey. */
pub_where = NULL;
log_assert (pk);
if (photo)
{
int hasattrib = 0;
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->attrib_data != NULL)
{
hasattrib = 1;
break;
}
/* It is legal but bad for compatibility to add a photo ID to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a photo on a v3 key.
Don't bother to ask this if the key already has a photo - any
damage has already been done at that point. -dms */
if (pk->version == 3 && !hasattrib)
{
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP2-style key. "
"Adding a photo ID may cause some versions\n"
" of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a photo ID to "
"a PGP2-style key.\n"));
return 0;
}
}
uid = generate_photo_id (ctrl, pk, photo_name);
}
else
uid = generate_user_id (pub_keyblock, uidstring);
if (!uid)
{
if (uidstring)
{
write_status_error ("adduid", gpg_error (304));
log_error ("%s", _("Such a user ID already exists on this key!\n"));
}
return 0;
}
err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0,
keygen_add_std_prefs, pk, NULL);
if (err)
{
write_status_error ("keysig", err);
log_error ("signing failed: %s\n", gpg_strerror (err));
free_user_id (uid);
return 0;
}
/* Insert/append to public keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = uid;
node = new_kbnode (pkt);
if (pub_where)
insert_kbnode (pub_where, node, 0);
else
add_kbnode (pub_keyblock, node);
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
if (pub_where)
insert_kbnode (node, new_kbnode (pkt), 0);
else
add_kbnode (pub_keyblock, new_kbnode (pkt));
return 1;
}
/*
* Remove all selected userids from the keyring
*/
static void
menu_deluid (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
selected = node->flag & NODFLG_SELUID;
if (selected)
{
/* Only cause a trust update if we delete a
non-revoked user id */
if (!node->pkt->pkt.user_id->flags.revoked)
update_trust = 1;
delete_kbnode (node);
}
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
selected = 0;
}
commit_kbnode (&pub_keyblock);
}
static int
menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock)
{
KBNODE node;
PKT_user_id *uid = NULL;
int changed = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL;
}
else if (uid && node->pkt->pkttype == PKT_SIGNATURE)
{
int okay, valid, selfsig, inv_sig, no_key, other_err;
tty_printf ("uid ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
okay = inv_sig = no_key = other_err = 0;
if (opt.with_colons)
valid = print_and_check_one_sig_colon (ctrl, pub_keyblock, node,
&inv_sig, &no_key,
&other_err, &selfsig, 1);
else
valid = print_and_check_one_sig (ctrl, pub_keyblock, node,
&inv_sig, &no_key, &other_err,
&selfsig, 1, 0);
if (valid)
{
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.valid",
_("Delete this good signature? (y/N/q)"));
/* Only update trust if we delete a good signature.
The other two cases do not affect trust. */
if (okay)
update_trust = 1;
}
else if (inv_sig || other_err)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.invalid",
_("Delete this invalid signature? (y/N/q)"));
else if (no_key)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.unknown",
_("Delete this unknown signature? (y/N/q)"));
if (okay == -1)
break;
if (okay && selfsig
&& !cpr_get_answer_is_yes
("keyedit.delsig.selfsig",
_("Really delete this self-signature? (y/N)")))
okay = 0;
if (okay)
{
delete_kbnode (node);
changed++;
}
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
uid = NULL;
}
if (changed)
{
commit_kbnode (&pub_keyblock);
tty_printf (ngettext("Deleted %d signature.\n",
"Deleted %d signatures.\n", changed), changed);
}
else
tty_printf (_("Nothing deleted.\n"));
return changed;
}
static int
menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only)
{
KBNODE uidnode;
int modified = 0, select_all = !count_selected_uids (keyblock);
for (uidnode = keyblock->next;
uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY;
uidnode = uidnode->next)
{
if (uidnode->pkt->pkttype == PKT_USER_ID
&& (uidnode->flag & NODFLG_SELUID || select_all))
{
int uids = 0, sigs = 0;
char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len,
0);
clean_one_uid (ctrl, keyblock, uidnode, opt.verbose, self_only, &uids,
&sigs);
if (uids)
{
const char *reason;
if (uidnode->pkt->pkt.user_id->flags.revoked)
reason = _("revoked");
else if (uidnode->pkt->pkt.user_id->flags.expired)
reason = _("expired");
else
reason = _("invalid");
tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason);
modified = 1;
}
else if (sigs)
{
tty_printf (ngettext("User ID \"%s\": %d signature removed\n",
"User ID \"%s\": %d signatures removed\n",
sigs), user, sigs);
modified = 1;
}
else
{
tty_printf (self_only == 1 ?
_("User ID \"%s\": already minimized\n") :
_("User ID \"%s\": already clean\n"), user);
}
xfree (user);
}
}
return modified;
}
/*
* Remove some of the secondary keys
*/
static void
menu_delkey (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
selected = node->flag & NODFLG_SELKEY;
if (selected)
delete_kbnode (node);
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else
selected = 0;
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys are no
longer used to certify other keys, so there is no change in
trust when revoking/removing them. */
}
/*
* Ask for a new revoker, create the self-signature and put it into
* the keyblock. Returns true if there is a new revoker.
*/
static int
menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive)
{
PKT_public_key *pk = NULL;
PKT_public_key *revoker_pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
struct revocation_key revkey;
size_t fprlen;
int rc;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
if (pk->numrevkeys == 0 && pk->version == 3)
{
/* It is legal but bad for compatibility to add a revoker to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a revoker on a v3 key.
Don't bother to ask this if the key already has a revoker -
any damage has already been done at that point. -dms */
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP 2.x-style key. "
"Adding a designated revoker may cause\n"
" some versions of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a designated revoker to "
"a PGP 2.x-style key.\n"));
return 0;
}
}
for (;;)
{
char *answer;
free_public_key (revoker_pk);
revoker_pk = xmalloc_clear (sizeof (*revoker_pk));
tty_printf ("\n");
answer = cpr_get_utf8
("keyedit.add_revoker",
_("Enter the user ID of the designated revoker: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
goto fail;
}
/* Note that I'm requesting CERT here, which usually implies
primary keys only, but some casual testing shows that PGP and
GnuPG both can handle a designated revocation from a subkey. */
revoker_pk->req_usage = PUBKEY_USAGE_CERT;
rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, revoker_pk, answer, NULL, NULL, 1);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (rc));
xfree (answer);
continue;
}
xfree (answer);
fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen);
if (fprlen != 20 && fprlen != 32)
{
log_error (_("cannot appoint a PGP 2.x style key as a "
"designated revoker\n"));
continue;
}
revkey.fprlen = fprlen;
revkey.class = 0x80;
if (sensitive)
revkey.class |= 0x40;
revkey.algid = revoker_pk->pubkey_algo;
if (cmp_public_keys (revoker_pk, pk) == 0)
{
/* This actually causes no harm (after all, a key that
designates itself as a revoker is the same as a
regular key), but it's easy enough to check. */
log_error (_("you cannot appoint a key as its own "
"designated revoker\n"));
continue;
}
keyid_from_pk (pk, NULL);
/* Does this revkey already exist? */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i;
for (i = 0; i < pk->numrevkeys; i++)
{
if (memcmp (&pk->revkey[i], &revkey,
sizeof (struct revocation_key)) == 0)
{
char buf[50];
log_error (_("this key has already been designated "
"as a revoker\n"));
format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf));
write_status_text (STATUS_ALREADY_SIGNED, buf);
break;
}
}
if (i < pk->numrevkeys)
continue;
}
print_key_info (ctrl, NULL, 0, revoker_pk, 0);
print_fingerprint (ctrl, NULL, revoker_pk, 2);
tty_printf ("\n");
tty_printf (_("WARNING: appointing a key as a designated revoker "
"cannot be undone!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay",
_("Are you sure you want to appoint this "
"key as a designated revoker? (y/N) ")))
continue;
free_public_key (revoker_pk);
revoker_pk = NULL;
break;
}
rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0,
keygen_add_revkey, &revkey, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto fail;
}
/* Insert into public keyblock. */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE);
return 1;
fail:
if (sig)
free_seckey_enc (sig);
free_public_key (revoker_pk);
return 0;
}
/* With FORCE_MAINKEY cleared this function handles the interactive
* menu option "expire". With UNATTENDED set to 1 this function only
* sets the expiration date of the primary key to NEWEXPIRATION and
* avoid all interactivity; with a value of 2 only the flagged subkeys
* are set to NEWEXPIRATION. Returns 0 if nothing was done,
* GPG_ERR_TRUE if the key was modified, or any other error code. */
static gpg_error_t
menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration)
{
int signumber, rc;
u32 expiredate;
int only_mainkey; /* Set if only the mainkey is to be updated. */
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
if (unattended)
{
only_mainkey = (unattended == 1);
expiredate = newexpiration;
}
else
{
int n1;
only_mainkey = 0;
n1 = count_selected_keys (pub_keyblock);
if (n1 > 1)
{
if (!cpr_get_answer_is_yes
("keyedit.expire_multiple_subkeys.okay",
_("Are you sure you want to change the"
" expiration time for multiple subkeys? (y/N) ")))
return gpg_error (GPG_ERR_CANCELED);;
}
else if (n1)
tty_printf (_("Changing expiration time for a subkey.\n"));
else
{
tty_printf (_("Changing expiration time for the primary key.\n"));
only_mainkey = 1;
no_primary_warning (pub_keyblock);
}
expiredate = ask_expiredate ();
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
signumber = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
main_pk->expiredate = expiredate;
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if ((node->flag & NODFLG_SELKEY) && unattended != 1)
{
/* The flag is set and we do not want to set the
* expiration date only for the main key. */
sub_pk = node->pkt->pkt.public_key;
sub_pk->expiredate = expiredate;
}
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (only_mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((only_mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!only_mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is a self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
signumber++;
if ((only_mainkey && main_pk->version < 4)
|| (!only_mainkey && sub_pk->version < 4))
{
log_info
(_("You can't change the expiration date of a v3 key\n"));
return gpg_error (GPG_ERR_LEGACY_KEY);
}
if (only_mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_expire,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_expire, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_TRUE)
rc = GPG_ERR_GENERAL;
return rc;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
}
}
}
update_trust = 1;
return gpg_error (GPG_ERR_TRUE);
}
/* Change the capability of a selected key. This command should only
* be used to rectify badly created keys and as such is not suggested
* for general use. */
static int
menu_changeusage (ctrl_t ctrl, kbnode_t keyblock)
{
int n1, rc;
int mainkey = 0;
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
n1 = count_selected_keys (keyblock);
if (n1 > 1)
{
tty_printf (_("You must select exactly one key.\n"));
return 0;
}
else if (n1)
tty_printf (_("Changing usage of a subkey.\n"));
else
{
tty_printf (_("Changing usage of the primary key.\n"));
mainkey = 1;
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->flag & NODFLG_SELKEY)
sub_pk = node->pkt->pkt.public_key;
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is the self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
if ((mainkey && main_pk->version < 4)
|| (!mainkey && sub_pk->version < 4))
{
/* Note: This won't happen because we don't support
* v3 keys anymore. */
log_info ("You can't change the capabilities of a v3 key\n");
return 0;
}
if (mainkey)
main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0,
main_pk->pubkey_usage);
else
sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1,
sub_pk->pubkey_usage);
if (mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_flags,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_flags, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
break;
}
}
}
return 1;
}
static int
menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock)
{
int rc, modified = 0;
PKT_public_key *main_pk;
KBNODE node;
u32 timestamp;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
merge_keys_and_selfsig (ctrl, pub_keyblock);
main_pk = pub_keyblock->pkt->pkt.public_key;
keyid_from_pk (main_pk, NULL);
/* We use the same timestamp for all backsigs so that we don't
reveal information about the used machine. */
timestamp = make_timestamp ();
for (node = pub_keyblock; node; node = node->next)
{
PKT_public_key *sub_pk = NULL;
KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/;
/* char *passphrase; */
/* Find a signing subkey with no backsig */
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG)
{
if (node->pkt->pkt.public_key->flags.backsig)
tty_printf (_
("signing subkey %s is already cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
else
sub_pk = node->pkt->pkt.public_key;
}
else
tty_printf (_("subkey %s does not sign and so does"
" not need to be cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
}
if (!sub_pk)
continue;
/* Find the selected selfsig on this subkey */
for (node2 = node->next;
node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next)
if (node2->pkt->pkt.signature->version >= 4
&& node2->pkt->pkt.signature->flags.chosen_selfsig)
{
sig_pk = node2;
break;
}
if (!sig_pk)
continue;
/* Find the secret subkey that matches the public subkey */
log_debug ("FIXME: Check whether a secret subkey is available.\n");
/* if (!sub_sk) */
/* { */
/* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */
/* keystr_from_pk (sub_pk)); */
/* continue; */
/* } */
/* Now we can get to work. */
rc = make_backsig (ctrl,
sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk,
timestamp, NULL);
if (!rc)
{
PKT_signature *newsig;
PACKET *newpkt;
rc = update_keysig_packet (ctrl,
&newsig, sig_pk->pkt->pkt.signature,
main_pk, NULL, sub_pk, main_pk,
NULL, NULL);
if (!rc)
{
/* Put the new sig into place on the pubkey */
newpkt = xmalloc_clear (sizeof (*newpkt));
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (sig_pk->pkt, NULL);
xfree (sig_pk->pkt);
sig_pk->pkt = newpkt;
modified = 1;
}
else
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
break;
}
}
else
{
log_error ("make_backsig failed: %s\n", gpg_strerror (rc));
break;
}
}
return modified;
}
static int
change_primary_uid_cb (PKT_signature * sig, void *opaque)
{
byte buf[1];
/* first clear all primary uid flags so that we are sure none are
* lingering around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID);
/* if opaque is set,we want to set the primary id */
if (opaque)
{
buf[0] = 1;
build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1);
}
return 0;
}
/*
* Set the primary uid flag for the selected UID. We will also reset
* all other primary uid flags. For this to work we have to update
* all the signature timestamps. If we would do this with the current
* time, we lose quite a lot of information, so we use a kludge to
* do this: Just increment the timestamp by one second which is
* sufficient to updated a signature during import.
*/
static int
menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected;
int attribute = 0;
int modified = 0;
if (count_selected_uids (pub_keyblock) != 1)
{
tty_printf (_("Please select exactly one user ID.\n"));
return 0;
}
main_pk = NULL;
uid = NULL;
selected = 0;
/* Is our selected uid an attribute packet? */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID)
attribute = (node->pkt->pkt.user_id->attrib_data != NULL);
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = node->flag & NODFLG_SELUID;
}
else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& attribute == (uid->attrib_data != NULL)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced.
We can just ignore v3 signatures because they are
not able to carry the primary ID flag. We also
ignore self-sigs on user IDs that are not of the
same type that we are making primary. That is, if
we are making a user ID primary, we alter user IDs.
If we are making an attribute packet primary, we
alter attribute packets. */
/* FIXME: We must make sure that we only have one
self-signature per user ID here (not counting
revocations) */
PKT_signature *newsig;
PACKET *newpkt;
const byte *p;
int action;
/* See whether this signature has the primary UID flag. */
p = parse_sig_subpkt (sig, 1,
SIGSUBPKT_PRIMARY_UID, NULL);
if (!p)
p = parse_sig_subpkt (sig, 0,
SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p) /* yes */
action = selected ? 0 : -1;
else /* no */
action = selected ? 1 : 0;
if (action)
{
int rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
change_primary_uid_cb,
action > 0 ? "x" : NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
}
return modified;
}
/*
* Set preferences to new values for the selected user IDs
*/
static int
menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
no_primary_warning (pub_keyblock);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user-ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the preferences. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL, main_pk,
keygen_upd_std_prefs, NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
return modified;
}
static int
menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer, *uri;
no_primary_warning (pub_keyblock);
if (url)
answer = xstrdup (url);
else
{
answer = cpr_get_utf8 ("keyedit.add_keyserver",
_("Enter your preferred keyserver URL: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (ascii_strcasecmp (answer, "none") == 0)
uri = NULL;
else
{
struct keyserver_spec *keyserver = NULL;
/* Sanity check the format */
keyserver = parse_keyserver_uri (answer, 1);
xfree (answer);
if (!keyserver)
{
log_info (_("could not parse keyserver URL\n"));
return 0;
}
uri = xstrdup (keyserver->uri);
free_keyserver_spec (keyserver);
}
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the subpacket. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
const byte *p;
size_t plen;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_KS, &plen);
if (p && plen)
{
tty_printf ("Current preferred keyserver for user"
" ID \"%s\": ", user);
tty_print_utf8_string (p, plen);
tty_printf ("\n");
if (!cpr_get_answer_is_yes
("keyedit.confirm_keyserver",
uri
? _("Are you sure you want to replace it? (y/N) ")
: _("Are you sure you want to delete it? (y/N) ")))
continue;
}
else if (uri == NULL)
{
/* There is no current keyserver URL, so there
is no point in trying to un-set it. */
continue;
}
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_keyserver_url, uri);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
xfree (uri);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
xfree (user);
}
}
}
xfree (uri);
return modified;
}
static int
menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer;
struct notation *notation;
no_primary_warning (pub_keyblock);
if (string)
answer = xstrdup (string);
else
{
answer = cpr_get_utf8 ("keyedit.add_notation",
_("Enter the notation: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (!ascii_strcasecmp (answer, "none")
|| !ascii_strcasecmp (answer, "-"))
notation = NULL; /* Delete them all. */
else
{
notation = string_to_notation (answer, 0);
if (!notation)
{
xfree (answer);
return 0;
}
}
xfree (answer);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
PKT_signature *newsig;
PACKET *newpkt;
int rc, skip = 0, addonly = 1;
if (sig->flags.notation)
{
tty_printf ("Current notations for user ID \"%s\":\n",
user);
tty_print_notations (-9, sig);
}
else
{
tty_printf ("No notations on user ID \"%s\"\n", user);
if (notation == NULL)
{
/* There are no current notations, so there
is no point in trying to un-set them. */
continue;
}
}
if (notation)
{
struct notation *n;
int deleting = 0;
notation->next = sig_to_notation (sig);
for (n = notation->next; n; n = n->next)
if (strcmp (n->name, notation->name) == 0)
{
if (notation->value)
{
if (strcmp (n->value, notation->value) == 0)
{
if (notation->flags.ignore)
{
/* Value match with a delete
flag. */
n->flags.ignore = 1;
deleting = 1;
}
else
{
/* Adding the same notation
twice, so don't add it at
all. */
skip = 1;
tty_printf ("Skipping notation:"
" %s=%s\n",
notation->name,
notation->value);
break;
}
}
}
else
{
/* No value, so it means delete. */
n->flags.ignore = 1;
deleting = 1;
}
if (n->flags.ignore)
{
tty_printf ("Removing notation: %s=%s\n",
n->name, n->value);
addonly = 0;
}
}
if (!notation->flags.ignore && !skip)
tty_printf ("Adding notation: %s=%s\n",
notation->name, notation->value);
/* We tried to delete, but had no matches. */
if (notation->flags.ignore && !deleting)
continue;
}
else
{
tty_printf ("Removing all notations\n");
addonly = 0;
}
if (skip
|| (!addonly
&&
!cpr_get_answer_is_yes ("keyedit.confirm_notation",
_("Proceed? (y/N) "))))
continue;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_notations, notation);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
free_notation (notation);
xfree (user);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
if (notation)
{
/* Snip off the notation list from the sig */
free_notation (notation->next);
notation->next = NULL;
}
xfree (user);
}
}
}
}
free_notation (notation);
return modified;
}
/*
* Select one user id or remove all selection if IDX is 0 or select
* all if IDX is -1. Returns: True if the selection changed.
*/
static int
menu_select_uid (KBNODE keyblock, int idx)
{
KBNODE node;
int i;
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag |= NODFLG_SELUID;
return 1;
}
else if (idx) /* Toggle. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No user ID with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (++i == idx)
{
if ((node->flag & NODFLG_SELUID))
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
}
}
}
}
else /* Unselect all */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag &= ~NODFLG_SELUID;
}
return 1;
}
/* Search in the keyblock for a uid that matches namehash */
static int
menu_select_uid_namehash (KBNODE keyblock, const char *namehash)
{
byte hash[NAMEHASH_LEN];
KBNODE node;
int i;
log_assert (strlen (namehash) == NAMEHASH_LEN * 2);
for (i = 0; i < NAMEHASH_LEN; i++)
hash[i] = hextobyte (&namehash[i * 2]);
for (node = keyblock->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
namehash_from_uid (node->pkt->pkt.user_id);
if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) ==
0)
{
if (node->flag & NODFLG_SELUID)
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
break;
}
}
}
if (!node)
{
tty_printf (_("No user ID with hash %s\n"), namehash);
return 0;
}
return 1;
}
/*
* Select secondary keys
* Returns: True if the selection changed.
*/
static int
menu_select_key (KBNODE keyblock, int idx, char *p)
{
KBNODE node;
int i, j;
int is_hex_digits;
is_hex_digits = p && strlen (p) >= 8;
if (is_hex_digits)
{
/* Skip initial spaces. */
while (spacep (p))
p ++;
/* If the id starts with 0x accept and ignore it. */
if (p[0] == '0' && p[1] == 'x')
p += 2;
for (i = 0, j = 0; p[i]; i ++)
if (hexdigitp (&p[i]))
{
p[j] = toupper (p[i]);
j ++;
}
else if (spacep (&p[i]))
/* Skip spaces. */
{
}
else
{
is_hex_digits = 0;
break;
}
if (is_hex_digits)
/* In case we skipped some spaces, add a new NUL terminator. */
{
p[j] = 0;
/* If we skipped some spaces, make sure that we still have
at least 8 characters. */
is_hex_digits = (/* Short keyid. */
strlen (p) == 8
/* Long keyid. */
|| strlen (p) == 16
/* Fingerprints are (currently) 32 or 40
characters. */
|| strlen (p) >= 32);
}
}
if (is_hex_digits)
{
int found_one = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
int match = 0;
if (strlen (p) == 8 || strlen (p) == 16)
{
u32 kid[2];
char kid_str[17];
keyid_from_pk (node->pkt->pkt.public_key, kid);
format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG,
kid_str, sizeof (kid_str));
if (strcmp (p, kid_str) == 0)
match = 1;
}
else
{
char fp[2*MAX_FINGERPRINT_LEN + 1];
hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp));
if (strcmp (fp, p) == 0)
match = 1;
}
if (match)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
found_one = 1;
}
}
if (found_one)
return 1;
tty_printf (_("No subkey with key ID '%s'.\n"), p);
return 0;
}
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag |= NODFLG_SELKEY;
}
else if (idx) /* Toggle selection. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No subkey with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
}
}
}
else /* Unselect all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag &= ~NODFLG_SELKEY;
}
return 1;
}
static int
count_uids_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag))
i++;
return i;
}
static int
count_keys_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag))
i++;
return i;
}
static int
count_uids (KBNODE keyblock)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
i++;
return i;
}
/*
* Returns true if there is at least one selected user id
*/
static int
count_selected_uids (KBNODE keyblock)
{
return count_uids_with_flag (keyblock, NODFLG_SELUID);
}
static int
count_selected_keys (KBNODE keyblock)
{
return count_keys_with_flag (keyblock, NODFLG_SELKEY);
}
/* Returns how many real (i.e. not attribute) uids are unmarked. */
static int
real_uids_left (KBNODE keyblock)
{
KBNODE node;
int real = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) &&
!node->pkt->pkt.user_id->attrib_data)
real++;
return real;
}
/*
* Ask whether the signature should be revoked. If the user commits this,
* flag bit MARK_A is set on the signature and the user ID.
*/
static void
ask_revoke_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node)
{
int doit = 0;
PKT_user_id *uid;
PKT_signature *sig = node->pkt->pkt.signature;
KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
if (!unode)
{
log_error ("Oops: no user ID for signature\n");
return;
}
uid = unode->pkt->pkt.user_id;
if (opt.with_colons)
{
if (uid->attrib_data)
printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len);
else
{
es_printf ("uid:::::::::");
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
}
es_printf ("\n");
print_and_check_one_sig_colon (ctrl, keyblock, node,
NULL, NULL, NULL, NULL, 1);
}
else
{
char *p = utf8_to_native (unode->pkt->pkt.user_id->name,
unode->pkt->pkt.user_id->len, 0);
tty_printf (_("user ID: \"%s\"\n"), p);
xfree (p);
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"), "");
}
if (sig->flags.expired)
{
tty_printf (_("This signature expired on %s.\n"),
expirestr_from_sig (sig));
/* Use a different question so we can have different help text */
doit = cpr_get_answer_is_yes
("ask_revoke_sig.expired",
_("Are you sure you still want to revoke it? (y/N) "));
}
else
doit = cpr_get_answer_is_yes
("ask_revoke_sig.one",
_("Create a revocation certificate for this signature? (y/N) "));
if (doit)
{
node->flag |= NODFLG_MARK_A;
unode->flag |= NODFLG_MARK_A;
}
}
/*
* Display all user ids of the current public key together with signatures
* done by one of our keys. Then walk over all this sigs and ask the user
* whether he wants to revoke this signature.
* Return: True when the keyblock has changed.
*/
static int
menu_revsig (ctrl_t ctrl, kbnode_t keyblock)
{
PKT_signature *sig;
PKT_public_key *primary_pk;
KBNODE node;
int changed = 0;
int rc, any, skip = 1, all = !count_selected_uids (keyblock);
struct revocation_reason_info *reason = NULL;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* First check whether we have any signatures at all. */
any = 0;
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
skip = 0;
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
any = 1;
break;
}
}
}
if (!any)
{
tty_printf (_("Not signed by you.\n"));
return 0;
}
/* FIXME: detect duplicates here */
tty_printf (_("You have signed these user IDs on key %s:\n"),
keystr_from_pk (keyblock->pkt->pkt.public_key));
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
/* Hmmm: Should we show only UIDs with a signature? */
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
skip = 0;
}
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"),
sig->flags.revocable ? "" : _(" (non-revocable)"));
if (sig->flags.revocable)
node->flag |= NODFLG_SELSIG;
}
else if (sig->sig_class == 0x30)
{
tty_printf (" ");
tty_printf (_("revoked by your key %s on %s\n"),
keystr (sig->keyid), datestr_from_sig (sig));
}
}
}
tty_printf ("\n");
/* ask */
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_SELSIG))
continue;
ask_revoke_sig (ctrl, keyblock, node);
}
/* present selected */
any = 0;
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_MARK_A))
continue;
if (!any)
{
any = 1;
tty_printf (_("You are about to revoke these signatures:\n"));
}
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
sig = node->pkt->pkt.signature;
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig), "",
sig->flags.exportable ? "" : _(" (non-exportable)"));
}
}
if (!any)
return 0; /* none selected */
if (!cpr_get_answer_is_yes
("ask_revoke_sig.okay",
_("Really create the revocation certificates? (y/N) ")))
return 0; /* forget it */
reason = ask_revocation_reason (0, 1, 0);
if (!reason)
{ /* user decided to cancel */
return 0;
}
/* now we can sign the user ids */
reloop: /* (must use this, because we are modifying the list) */
primary_pk = keyblock->pkt->pkt.public_key;
for (node = keyblock; node; node = node->next)
{
KBNODE unode;
PACKET *pkt;
struct sign_attrib attrib;
PKT_public_key *signerkey;
if (!(node->flag & NODFLG_MARK_A)
|| node->pkt->pkttype != PKT_SIGNATURE)
continue;
unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
log_assert (unode); /* we already checked this */
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable;
node->flag &= ~NODFLG_MARK_A;
signerkey = xmalloc_secure_clear (sizeof *signerkey);
if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid))
{
log_info (_("no secret key\n"));
free_public_key (signerkey);
continue;
}
rc = make_keysig_packet (ctrl, &sig, primary_pk,
unode->pkt->pkt.user_id,
NULL, signerkey, 0x30, 0, 0,
sign_mk_attrib, &attrib, NULL);
free_public_key (signerkey);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
update_trust = 1;
/* Are we revoking our own uid? */
if (primary_pk->keyid[0] == sig->keyid[0] &&
primary_pk->keyid[1] == sig->keyid[1])
unode->pkt->pkt.user_id->flags.revoked = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (unode, new_kbnode (pkt), 0);
goto reloop;
}
release_revocation_reason_info (reason);
return changed;
}
/* return 0 if revocation of NODE (which must be a User ID) was
successful, non-zero if there was an error. *modified will be set
to 1 if a change was made. */
static int
core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason, int *modified)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
gpg_error_t rc;
if (node->pkt->pkttype != PKT_USER_ID)
{
rc = gpg_error (GPG_ERR_NO_USER_ID);
write_status_error ("keysig", rc);
log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->flags.revoked)
{
char *user = utf8_to_native (uid->name, uid->len, 0);
log_info (_("user ID \"%s\" is already revoked\n"), user);
xfree (user);
}
else
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
u32 timestamp = make_timestamp ();
if (uid->created >= timestamp)
{
/* Okay, this is a problem. The user ID selfsig was
created in the future, so we need to warn the user and
set our revocation timestamp one second after that so
everything comes out clean. */
log_info (_("WARNING: a user ID signature is dated %d"
" seconds in the future\n"),
uid->created - timestamp);
timestamp = uid->created + 1;
}
memset (&attrib, 0, sizeof attrib);
/* should not need to cast away const here; but
revocation_reason_build_cb needs to take a non-const
void* in order to meet the function signutare for the
mksubpkt argument to make_keysig_packet */
attrib.reason = (struct revocation_reason_info *)reason;
rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30,
timestamp, 0,
sign_mk_attrib, &attrib, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
#ifndef NO_TRUST_MODELS
/* If the trustdb has an entry for this key+uid then the
trustdb needs an update. */
if (!update_trust
&& ((get_validity (ctrl, keyblock, pk, uid, NULL, 0)
& TRUST_MASK)
>= TRUST_UNDEFINED))
update_trust = 1;
#endif /*!NO_TRUST_MODELS*/
node->pkt->pkt.user_id->flags.revoked = 1;
if (modified)
*modified = 1;
}
}
return 0;
}
}
/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if
keyblock changed. */
static int
menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
size_t valid_uids;
/* Note that this is correct as per the RFCs, but nevertheless
somewhat meaningless in the real world. 1991 did define the 0x30
sig class, but PGP 2.x did not actually implement it, so it would
probably be safe to use v4 revocations everywhere. -ds */
for (node = pub_keyblock; node; node = node->next)
if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->selfsigversion > 3))
{
if ((reason = ask_revocation_reason (0, 1, 4)))
break;
else
goto leave;
}
/* Too make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = pub_keyblock; node; node = node->next)
valid_uids +=
node->pkt->pkttype == PKT_USER_ID
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired;
reloop: /* (better this way because we are modifying the keyring) */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID))
{
int modified = 0;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("Cannot revoke the last valid user ID.\n"));
goto leave;
}
rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified);
if (rc)
goto leave;
if (modified)
{
node->flag &= ~NODFLG_SELUID;
changed = 1;
goto reloop;
}
}
if (changed)
commit_kbnode (&pub_keyblock);
leave:
release_revocation_reason_info (reason);
return changed;
}
/*
* Revoke the whole key.
*/
static int
menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
int rc, changed = 0;
struct revocation_reason_info *reason;
PACKET *pkt;
PKT_signature *sig;
if (pk->flags.revoked)
{
tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk));
return 0;
}
reason = ask_revocation_reason (1, 0, 0);
/* user decided to cancel */
if (!reason)
return 0;
rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk,
0x20, 0, 0,
revocation_reason_build_cb, reason, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto scram;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), 0);
commit_kbnode (&pub_keyblock);
update_trust = 1;
scram:
release_revocation_reason_info (reason);
return changed;
}
static int
menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *mainpk;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
reason = ask_revocation_reason (1, 0, 0);
if (!reason)
return 0; /* User decided to cancel. */
reloop: /* (better this way because we are modifying the keyring) */
mainpk = pub_keyblock->pkt->pkt.public_key;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (node->flag & NODFLG_SELKEY))
{
PACKET *pkt;
PKT_signature *sig;
PKT_public_key *subpk = node->pkt->pkt.public_key;
struct sign_attrib attrib;
if (subpk->flags.revoked)
{
tty_printf (_("Subkey %s is already revoked.\n"),
keystr_from_pk (subpk));
continue;
}
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
node->flag &= ~NODFLG_SELKEY;
rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk,
0x28, 0, 0, sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
goto reloop;
}
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys no longer
are used to certify other keys, so there is no change in trust
when revoking/removing them */
release_revocation_reason_info (reason);
return changed;
}
/* Note that update_ownertrust is going to mark the trustdb dirty when
enabling or disabling a key. This is arguably sub-optimal as
disabled keys are still counted in the web of trust, but perhaps
not worth adding extra complexity to change. -ds */
#ifndef NO_TRUST_MODELS
static int
enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable)
{
PKT_public_key *pk =
find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
unsigned int trust, newtrust;
trust = newtrust = get_ownertrust (ctrl, pk);
newtrust &= ~TRUST_FLAG_DISABLED;
if (disable)
newtrust |= TRUST_FLAG_DISABLED;
if (trust == newtrust)
return 0; /* already in that state */
update_ownertrust (ctrl, pk, newtrust);
return 0;
}
#endif /*!NO_TRUST_MODELS*/
static void
menu_showphoto (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE node;
int select_all = !count_selected_uids (keyblock);
int count = 0;
PKT_public_key *pk = NULL;
/* Look for the public key first. We have to be really, really,
explicit as to which photo this is, and what key it is a UID on
since people may want to sign it. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
count++;
if ((select_all || (node->flag & NODFLG_SELUID)) &&
uid->attribs != NULL)
{
int i;
for (i = 0; i < uid->numattribs; i++)
{
byte type;
u32 size;
if (uid->attribs[i].type == ATTRIB_IMAGE &&
parse_image_header (&uid->attribs[i], &type, &size))
{
tty_printf (_("Displaying %s photo ID of size %ld for "
"key %s (uid %d)\n"),
image_type_to_string (type, 1),
(ulong) size, keystr_from_pk (pk), count);
show_photos (ctrl, &uid->attribs[i], 1, pk, uid);
}
}
}
}
}
}
diff --git a/g10/keygen.c b/g10/keygen.c
index 2aed71d5b..4917a36a9 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -1,6115 +1,6115 @@
/* keygen.c - Generate a key pair
* Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2015, 2016, 2017, 2018 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "../common/ttyio.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "pkglue.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
/* The default algorithms. If you change them, you should ensure the value
is inside the bounds enforced by ask_keysize and gen_xxx. See also
get_keysize_range which encodes the allowed ranges. */
#define DEFAULT_STD_KEY_PARAM "rsa3072/cert,sign+rsa3072/encr"
#define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr"
/* When generating keys using the streamlined key generation dialog,
use this as a default expiration interval. */
const char *default_expiration_interval = "2y";
/* Flag bits used during key generation. */
#define KEYGEN_FLAG_NO_PROTECTION 1
#define KEYGEN_FLAG_TRANSIENT_KEY 2
#define KEYGEN_FLAG_CREATE_V5_KEY 4
/* Maximum number of supported algorithm preferences. */
#define MAX_PREFS 30
enum para_name {
pKEYTYPE,
pKEYLENGTH,
pKEYCURVE,
pKEYUSAGE,
pSUBKEYTYPE,
pSUBKEYLENGTH,
pSUBKEYCURVE,
pSUBKEYUSAGE,
pAUTHKEYTYPE,
pNAMEREAL,
pNAMEEMAIL,
pNAMECOMMENT,
pPREFERENCES,
pREVOKER,
pUSERID,
pCREATIONDATE,
pKEYCREATIONDATE, /* Same in seconds since epoch. */
pEXPIREDATE,
pKEYEXPIRE, /* in n seconds */
pSUBKEYCREATIONDATE,
pSUBKEYEXPIRE, /* in n seconds */
pAUTHKEYCREATIONDATE, /* Not yet used. */
pPASSPHRASE,
pSERIALNO,
pCARDBACKUPKEY,
pHANDLE,
pKEYSERVER,
pKEYGRIP,
pSUBKEYGRIP,
pVERSION, /* Desired version of the key packet. */
pSUBVERSION, /* Ditto for the subpacket. */
pCARDKEY /* The keygrips have been taken from active card (bool). */
};
struct para_data_s {
struct para_data_s *next;
int lnr;
enum para_name key;
union {
u32 expire;
u32 creation;
int abool;
unsigned int usage;
struct revocation_key revkey;
char value[1];
} u;
};
struct output_control_s
{
int lnr;
int dryrun;
unsigned int keygen_flags;
int use_files;
struct {
char *fname;
char *newfname;
IOBUF stream;
armor_filter_context_t *afx;
} pub;
};
struct opaque_data_usage_and_pk {
unsigned int usage;
PKT_public_key *pk;
};
/* FIXME: These globals vars are ugly. And using MAX_PREFS even for
* aeads is useless, given that we don't expects more than a very few
* algorithms. */
static int prefs_initialized = 0;
static byte sym_prefs[MAX_PREFS];
static int nsym_prefs;
static byte hash_prefs[MAX_PREFS];
static int nhash_prefs;
static byte zip_prefs[MAX_PREFS];
static int nzip_prefs;
static byte aead_prefs[MAX_PREFS];
static int naead_prefs;
static int mdc_available;
static int ks_modify;
static int aead_available;
static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage,
u32 *r_expire, unsigned int *r_nbits,
const char **r_curve, int *r_version,
char **r_keygrip, u32 *r_keytime);
static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card );
static int write_keyblock (iobuf_t out, kbnode_t node);
static gpg_error_t gen_card_key (int keyno, int algo, int is_primary,
kbnode_t pub_root, u32 *timestamp,
u32 expireval, int keygen_flags);
static unsigned int get_keysize_range (int algo,
unsigned int *min, unsigned int *max);
/* Return the algo string for a default new key. */
const char *
get_default_pubkey_algo (void)
{
if (opt.def_new_key_algo)
{
if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':'))
return opt.def_new_key_algo;
/* To avoid checking that option every time we delay that until
* here. The only thing we really need to make sure is that
* there is no colon in the string so that the --gpgconf-list
* command won't mess up its output. */
log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo");
}
return DEFAULT_STD_KEY_PARAM;
}
static void
print_status_key_created (int letter, PKT_public_key *pk, const char *handle)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char *buf, *p;
size_t i, n;
if (!handle)
handle = "";
buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1);
p = buf;
if (letter || pk)
{
*p++ = letter;
if (pk)
{
*p++ = ' ';
fingerprint_from_pk (pk, array, &n);
s = array;
/* Fixme: Use bin2hex */
for (i=0; i < n ; i++, s++, p += 2)
snprintf (p, 3, "%02X", *s);
}
}
if (*handle)
{
*p++ = ' ';
for (i=0; handle[i] && i < 100; i++)
*p++ = isspace ((unsigned int)handle[i])? '_':handle[i];
}
*p = 0;
write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED,
buf);
xfree (buf);
}
static void
print_status_key_not_created (const char *handle)
{
print_status_key_created (0, NULL, handle);
}
static gpg_error_t
write_uid (kbnode_t root, const char *s)
{
PACKET *pkt = xmalloc_clear (sizeof *pkt);
size_t n = strlen (s);
if (n > MAX_UID_PACKET_LENGTH - 10)
return gpg_error (GPG_ERR_INV_USER_ID);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n);
pkt->pkt.user_id->len = n;
pkt->pkt.user_id->ref = 1;
strcpy (pkt->pkt.user_id->name, s);
add_kbnode (root, new_kbnode (pkt));
return 0;
}
static void
do_add_key_flags (PKT_signature *sig, unsigned int use)
{
byte buf[1];
buf[0] = 0;
/* The spec says that all primary keys MUST be able to certify. */
if(sig->sig_class!=0x18)
buf[0] |= 0x01;
if (use & PUBKEY_USAGE_SIG)
buf[0] |= 0x02;
if (use & PUBKEY_USAGE_ENC)
buf[0] |= 0x04 | 0x08;
if (use & PUBKEY_USAGE_AUTH)
buf[0] |= 0x20;
build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, 1);
}
int
keygen_add_key_expire (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
byte buf[8];
u32 u;
if (pk->expiredate)
{
if (pk->expiredate > pk->timestamp)
u = pk->expiredate - pk->timestamp;
else
u = 1;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4);
}
else
{
/* Make sure we don't leave a key expiration subpacket lying
around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE);
}
return 0;
}
/* Add the key usage (i.e. key flags) in SIG from the public keys
* pubkey_usage field. OPAQUE has the public key. */
int
keygen_add_key_flags (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
return 0;
}
static int
keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque)
{
struct opaque_data_usage_and_pk *oduap = opaque;
do_add_key_flags (sig, oduap->usage);
return keygen_add_key_expire (sig, oduap->pk);
}
static int
set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf)
{
int i;
for (i=0; i < *nbuf; i++ )
if (buf[i] == val)
{
log_info (_("preference '%s' duplicated\n"), item);
return -1;
}
if (*nbuf >= MAX_PREFS)
{
if(type==1)
log_info(_("too many cipher preferences\n"));
else if(type==2)
log_info(_("too many digest preferences\n"));
else if(type==3)
log_info(_("too many compression preferences\n"));
else if(type==4)
log_info(_("too many AEAD preferences\n"));
else
BUG();
return -1;
}
buf[(*nbuf)++] = val;
return 0;
}
/*
* Parse the supplied string and use it to set the standard
* preferences. The string may be in a form like the one printed by
* "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual
* cipher/hash/compress names. Use NULL to set the default
* preferences. Returns: 0 = okay
*/
int
keygen_set_std_prefs (const char *string,int personal)
{
byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS], aead[MAX_PREFS];
int nsym=0, nhash=0, nzip=0, naead=0, val, rc=0;
int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */
char dummy_string[25*4+1]; /* Enough for 25 items. */
if (!string || !ascii_strcasecmp (string, "default"))
{
if (opt.def_preference_list)
string=opt.def_preference_list;
else
{
int any_compress = 0;
dummy_string[0]='\0';
/* The rationale why we use the order AES256,192,128 is
for compatibility reasons with PGP. If gpg would
define AES128 first, we would get the somewhat
confusing situation:
gpg -r pgpkey -r gpgkey ---gives--> AES256
gpg -r gpgkey -r pgpkey ---gives--> AES
Note that by using --personal-cipher-preferences it is
possible to prefer AES128.
*/
/* Make sure we do not add more than 15 items here, as we
could overflow the size of dummy_string. We currently
have at most 12. */
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) )
strcat(dummy_string,"S9 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) )
strcat(dummy_string,"S8 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) )
strcat(dummy_string,"S7 ");
strcat(dummy_string,"S2 "); /* 3DES */
if (opt.flags.rfc4880bis && !openpgp_aead_test_algo (AEAD_ALGO_OCB))
strcat(dummy_string,"A2 ");
if (opt.flags.rfc4880bis && !openpgp_aead_test_algo (AEAD_ALGO_EAX))
strcat(dummy_string,"A1 ");
if (personal)
{
/* The default internal hash algo order is:
* SHA-256, SHA-384, SHA-512, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
}
else
{
/* The default advertised hash algo order is:
* SHA-512, SHA-384, SHA-256, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
}
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224))
strcat (dummy_string, "H11 ");
strcat (dummy_string, "H2 "); /* SHA-1 */
if(!check_compress_algo(COMPRESS_ALGO_ZLIB))
{
strcat(dummy_string,"Z2 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_BZIP2))
{
strcat(dummy_string,"Z3 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_ZIP))
{
strcat(dummy_string,"Z1 ");
any_compress = 1;
}
/* In case we have no compress algo at all, declare that
we prefer no compression. */
if (!any_compress)
strcat(dummy_string,"Z0 ");
/* Remove the trailing space. */
if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ')
dummy_string[strlen (dummy_string)-1] = 0;
string=dummy_string;
}
}
else if (!ascii_strcasecmp (string, "none"))
string = "";
if(strlen(string))
{
char *prefstringbuf;
char *tok, *prefstring;
/* We need a writable string. */
prefstring = prefstringbuf = xstrdup (string);
while((tok=strsep(&prefstring," ,")))
{
if((val=string_to_cipher_algo (tok)))
{
if(set_one_pref(val,1,tok,sym,&nsym))
rc=-1;
}
else if((val=string_to_digest_algo (tok)))
{
if(set_one_pref(val,2,tok,hash,&nhash))
rc=-1;
}
else if((val=string_to_compress_algo(tok))>-1)
{
if(set_one_pref(val,3,tok,zip,&nzip))
rc=-1;
}
else if ((val=string_to_aead_algo (tok)))
{
if (set_one_pref (val, 4, tok, aead, &naead))
rc = -1;
}
else if (ascii_strcasecmp(tok,"mdc")==0)
mdc=1;
else if (ascii_strcasecmp(tok,"no-mdc")==0)
mdc=0;
else if (ascii_strcasecmp(tok,"ks-modify")==0)
modify=1;
else if (ascii_strcasecmp(tok,"no-ks-modify")==0)
modify=0;
else
{
log_info (_("invalid item '%s' in preference string\n"),tok);
rc=-1;
}
}
xfree (prefstringbuf);
}
if(!rc)
{
if(personal)
{
if(personal==PREFTYPE_SYM)
{
xfree(opt.personal_cipher_prefs);
if(nsym==0)
opt.personal_cipher_prefs=NULL;
else
{
int i;
opt.personal_cipher_prefs=
xmalloc(sizeof(prefitem_t *)*(nsym+1));
for (i=0; i<nsym; i++)
{
opt.personal_cipher_prefs[i].type = PREFTYPE_SYM;
opt.personal_cipher_prefs[i].value = sym[i];
}
opt.personal_cipher_prefs[i].type = PREFTYPE_NONE;
opt.personal_cipher_prefs[i].value = 0;
}
}
else if (personal == PREFTYPE_AEAD)
{
xfree(opt.personal_aead_prefs);
if (!naead)
opt.personal_aead_prefs = NULL;
else
{
int i;
opt.personal_aead_prefs=
xmalloc(sizeof(prefitem_t *)*(naead+1));
for (i=0; i<naead; i++)
{
opt.personal_aead_prefs[i].type = PREFTYPE_AEAD;
opt.personal_aead_prefs[i].value = sym[i];
}
opt.personal_aead_prefs[i].type = PREFTYPE_NONE;
opt.personal_aead_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_HASH)
{
xfree(opt.personal_digest_prefs);
if(nhash==0)
opt.personal_digest_prefs=NULL;
else
{
int i;
opt.personal_digest_prefs=
xmalloc(sizeof(prefitem_t *)*(nhash+1));
for (i=0; i<nhash; i++)
{
opt.personal_digest_prefs[i].type = PREFTYPE_HASH;
opt.personal_digest_prefs[i].value = hash[i];
}
opt.personal_digest_prefs[i].type = PREFTYPE_NONE;
opt.personal_digest_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_ZIP)
{
xfree(opt.personal_compress_prefs);
if(nzip==0)
opt.personal_compress_prefs=NULL;
else
{
int i;
opt.personal_compress_prefs=
xmalloc(sizeof(prefitem_t *)*(nzip+1));
for (i=0; i<nzip; i++)
{
opt.personal_compress_prefs[i].type = PREFTYPE_ZIP;
opt.personal_compress_prefs[i].value = zip[i];
}
opt.personal_compress_prefs[i].type = PREFTYPE_NONE;
opt.personal_compress_prefs[i].value = 0;
}
}
}
else
{
memcpy (sym_prefs, sym, (nsym_prefs=nsym));
memcpy (hash_prefs, hash, (nhash_prefs=nhash));
memcpy (zip_prefs, zip, (nzip_prefs=nzip));
memcpy (aead_prefs, aead, (naead_prefs=naead));
mdc_available = mdc;
aead_available = !!naead;
ks_modify = modify;
prefs_initialized = 1;
}
}
return rc;
}
/* Return a fake user ID containing the preferences. Caller must
free. */
PKT_user_id *
keygen_get_std_prefs(void)
{
int i,j=0;
PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id));
if(!prefs_initialized)
keygen_set_std_prefs(NULL,0);
uid->ref=1;
uid->prefs = xmalloc ((sizeof(prefitem_t *)*
(nsym_prefs+naead_prefs+nhash_prefs+nzip_prefs+1)));
for(i=0;i<nsym_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_SYM;
uid->prefs[j].value=sym_prefs[i];
}
for (i=0; i < naead_prefs; i++, j++)
{
uid->prefs[j].type = PREFTYPE_AEAD;
uid->prefs[j].value = aead_prefs[i];
}
for(i=0;i<nhash_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_HASH;
uid->prefs[j].value=hash_prefs[i];
}
for(i=0;i<nzip_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_ZIP;
uid->prefs[j].value=zip_prefs[i];
}
uid->prefs[j].type=PREFTYPE_NONE;
uid->prefs[j].value=0;
uid->flags.mdc = mdc_available;
uid->flags.aead = aead_available;
uid->flags.ks_modify = ks_modify;
return uid;
}
static void
add_feature_mdc (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x01; /* MDC feature */
else
buf[0] &= ~0x01;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_aead (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x02)) || (!enabled && !(s[0] & 0x02))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x02; /* AEAD supported */
else
buf[0] &= ~0x02;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_v5 (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x04)) || (!enabled && !(s[0] & 0x04))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x04; /* v5 key supported */
else
buf[0] &= ~0x04;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_keyserver_modify (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
/* The keyserver modify flag is a negative flag (i.e. no-modify) */
enabled=!enabled;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_KS_FLAGS, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x80; /* no-modify flag */
else
buf[0] &= ~0x80;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS);
else
build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n);
xfree (buf);
}
int
keygen_upd_std_prefs (PKT_signature *sig, void *opaque)
{
(void)opaque;
if (!prefs_initialized)
keygen_set_std_prefs (NULL, 0);
if (nsym_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM);
}
if (naead_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_AEAD, aead_prefs, naead_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_AEAD);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_AEAD);
}
if (nhash_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH);
}
if (nzip_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR);
}
/* Make sure that the MDC feature flag is set if needed. */
add_feature_mdc (sig,mdc_available);
add_feature_aead (sig, aead_available);
add_feature_v5 (sig, opt.flags.rfc4880bis);
add_keyserver_modify (sig,ks_modify);
keygen_add_keyserver_url(sig,NULL);
return 0;
}
/****************
* Add preference to the self signature packet.
* This is only called for packets with version > 3.
*/
int
keygen_add_std_prefs (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
keygen_add_key_expire (sig, opaque );
keygen_upd_std_prefs (sig, opaque);
keygen_add_keyserver_url (sig,NULL);
return 0;
}
int
keygen_add_keyserver_url(PKT_signature *sig, void *opaque)
{
const char *url=opaque;
if(!url)
url=opt.def_keyserver_url;
if(url)
build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url));
else
delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS);
return 0;
}
int
keygen_add_notations(PKT_signature *sig,void *opaque)
{
struct notation *notation;
/* We always start clean */
delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION);
delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION);
sig->flags.notation=0;
for(notation=opaque;notation;notation=notation->next)
if(!notation->flags.ignore)
{
unsigned char *buf;
unsigned int n1,n2;
n1=strlen(notation->name);
if(notation->altvalue)
n2=strlen(notation->altvalue);
else if(notation->bdat)
n2=notation->blen;
else
n2=strlen(notation->value);
buf = xmalloc( 8 + n1 + n2 );
/* human readable or not */
buf[0] = notation->bdat?0:0x80;
buf[1] = buf[2] = buf[3] = 0;
buf[4] = n1 >> 8;
buf[5] = n1;
buf[6] = n2 >> 8;
buf[7] = n2;
memcpy(buf+8, notation->name, n1 );
if(notation->altvalue)
memcpy(buf+8+n1, notation->altvalue, n2 );
else if(notation->bdat)
memcpy(buf+8+n1, notation->bdat, n2 );
else
memcpy(buf+8+n1, notation->value, n2 );
build_sig_subpkt( sig, SIGSUBPKT_NOTATION |
(notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0),
buf, 8+n1+n2 );
xfree(buf);
}
return 0;
}
int
keygen_add_revkey (PKT_signature *sig, void *opaque)
{
struct revocation_key *revkey = opaque;
byte buf[2+MAX_FINGERPRINT_LEN];
log_assert (revkey->fprlen <= MAX_FINGERPRINT_LEN);
buf[0] = revkey->class;
buf[1] = revkey->algid;
memcpy (buf + 2, revkey->fpr, revkey->fprlen);
memset (buf + 2 + revkey->fprlen, 0, sizeof (revkey->fpr) - revkey->fprlen);
build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+revkey->fprlen);
/* All sigs with revocation keys set are nonrevocable. */
sig->flags.revocable = 0;
buf[0] = 0;
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
parse_revkeys (sig);
return 0;
}
/* Create a back-signature. If TIMESTAMP is not NULL, use it for the
signature creation time. */
gpg_error_t
make_backsig (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PKT_signature *backsig;
cache_public_key (sub_pk);
err = make_keysig_packet (ctrl, &backsig, pk, NULL, sub_pk, sub_psk, 0x19,
timestamp, 0, NULL, NULL, cache_nonce);
if (err)
log_error ("make_keysig_packet failed for backsig: %s\n",
gpg_strerror (err));
else
{
/* Get it into a binary packed form. */
IOBUF backsig_out = iobuf_temp();
PACKET backsig_pkt;
init_packet (&backsig_pkt);
backsig_pkt.pkttype = PKT_SIGNATURE;
backsig_pkt.pkt.signature = backsig;
err = build_packet (backsig_out, &backsig_pkt);
free_packet (&backsig_pkt, NULL);
if (err)
log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err));
else
{
size_t pktlen = 0;
byte *buf = iobuf_get_temp_buffer (backsig_out);
/* Remove the packet header. */
if(buf[0]&0x40)
{
if (buf[1] < 192)
{
pktlen = buf[1];
buf += 2;
}
else if(buf[1] < 224)
{
pktlen = (buf[1]-192)*256;
pktlen += buf[2]+192;
buf += 3;
}
else if (buf[1] == 255)
{
pktlen = buf32_to_size_t (buf+2);
buf += 6;
}
else
BUG ();
}
else
{
int mark = 1;
switch (buf[0]&3)
{
case 3:
BUG ();
break;
case 2:
pktlen = (size_t)buf[mark++] << 24;
pktlen |= buf[mark++] << 16;
/* fall through */
case 1:
pktlen |= buf[mark++] << 8;
/* fall through */
case 0:
pktlen |= buf[mark++];
}
buf += mark;
}
/* Now make the binary blob into a subpacket. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNATURE, buf, pktlen);
iobuf_close (backsig_out);
}
}
return err;
}
/* Write a direct key signature to the first key in ROOT using the key
PSK. REVKEY is describes the direct key signature and TIMESTAMP is
the timestamp to set on the signature. */
static gpg_error_t
write_direct_sig (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
struct revocation_key *revkey, u32 timestamp,
const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing direct signature\n"));
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, NULL,NULL, psk, 0x1F,
timestamp, 0,
keygen_add_revkey, revkey, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) );
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write a self-signature to the first user id in ROOT using the key
PSK. USE and TIMESTAMP give the extra data we need for the
signature. */
static gpg_error_t
write_selfsigs (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
PKT_user_id *uid;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing self signature\n"));
/* Get the uid packet from the list. */
node = find_kbnode (root, PKT_USER_ID);
if (!node)
BUG(); /* No user id packet in tree. */
uid = node->pkt->pkt.user_id;
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pk = node->pkt->pkt.public_key;
/* The usage has not yet been set - do it now. */
pk->pubkey_usage = use;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, psk, 0x13,
timestamp, 0,
keygen_add_std_prefs, pk, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err));
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write the key binding signature. If TIMESTAMP is not NULL use the
signature creation time. PRI_PSK is the key use for signing.
SUB_PSK is a key used to create a back-signature; that one is only
used if USE has the PUBKEY_USAGE_SIG capability. */
static int
write_keybinding (ctrl_t ctrl, kbnode_t root,
PKT_public_key *pri_psk, PKT_public_key *sub_psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pri_pk, *sub_pk;
struct opaque_data_usage_and_pk oduap;
if (opt.verbose)
log_info(_("writing key binding signature\n"));
/* Get the primary pk packet from the tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pri_pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
* signature creation is able to retrieve the public key. */
cache_public_key (pri_pk);
/* Find the last subkey. */
sub_pk = NULL;
for (node = root; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
}
if (!sub_pk)
BUG();
/* Make the signature. */
oduap.usage = use;
oduap.pk = sub_pk;
err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18,
timestamp, 0,
keygen_add_key_flags_and_expire, &oduap,
cache_nonce);
if (err)
{
log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err));
return err;
}
/* Make a backsig. */
if (use & PUBKEY_USAGE_SIG)
{
err = make_backsig (ctrl,
sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce);
if (err)
return err;
}
pkt = xmalloc_clear ( sizeof *pkt );
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt) );
return err;
}
static gpg_error_t
ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp, int algo)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *curve = NULL;
int i;
const char *oidstr;
unsigned int nbits;
array[0] = NULL;
array[1] = NULL;
array[2] = NULL;
list = gcry_sexp_find_token (sexp, "public-key", 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
l2 = gcry_sexp_find_token (list, "curve", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
curve = gcry_sexp_nth_string (l2, 1);
if (!curve)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
gcry_sexp_release (l2);
oidstr = openpgp_curve_to_oid (curve, &nbits, NULL);
if (!oidstr)
{
/* That can't happen because we used one of the curves
gpg_curve_to_oid knows about. */
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
err = openpgp_oid_from_str (oidstr, &array[0]);
if (err)
goto leave;
l2 = gcry_sexp_find_token (list, "q", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
array[1] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[1])
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
gcry_sexp_release (list);
if (algo == PUBKEY_ALGO_ECDH)
{
array[2] = pk_ecdh_default_params (nbits);
if (!array[2])
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
xfree (curve);
if (err)
{
for (i=0; i < 3; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
}
return err;
}
/* Extract key parameters from SEXP and store them in ARRAY. ELEMS is
a string where each character denotes a parameter name. TOPNAME is
the name of the top element above the elements. */
static int
key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp,
const char *topname, const char *elems)
{
gcry_sexp_t list, l2;
const char *s;
int i, idx;
int rc = 0;
list = gcry_sexp_find_token (sexp, topname, 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
for (idx=0,s=elems; *s; s++, idx++)
{
l2 = gcry_sexp_find_token (list, s, 1);
if (!l2)
{
rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */
goto leave;
}
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[idx])
{
rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */
goto leave;
}
}
gcry_sexp_release (list);
leave:
if (rc)
{
for (i=0; i<idx; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
gcry_sexp_release (list);
}
return rc;
}
/* Create a keyblock using the given KEYGRIP. ALGO is the OpenPGP
* algorithm of that keygrip. If CARDKEY is true the key is expected
* to already live on the active card. */
static int
do_create_from_keygrip (ctrl_t ctrl, int algo,
const char *hexkeygrip, int cardkey,
kbnode_t pub_root, u32 timestamp, u32 expireval,
int is_subkey, int keygen_flags)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
const char *algoelem;
if (hexkeygrip[0] == '&')
hexkeygrip++;
switch (algo)
{
case PUBKEY_ALGO_RSA: algoelem = "ne"; break;
case PUBKEY_ALGO_DSA: algoelem = "pqgy"; break;
case PUBKEY_ALGO_ELGAMAL_E: algoelem = "pgy"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA: algoelem = ""; break;
case PUBKEY_ALGO_EDDSA: algoelem = ""; break;
default: return gpg_error (GPG_ERR_INTERNAL);
}
/* Ask the agent for the public key matching HEXKEYGRIP. */
if (cardkey)
{
err = agent_scd_readkey (ctrl, hexkeygrip, &s_key, NULL);
if (err)
return err;
}
else
{
unsigned char *public;
err = agent_readkey (ctrl, 0, hexkeygrip, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
}
/* Build a public key packet. */
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/* Common code for the key generation function gen_xxx. */
static int
common_gen (const char *keyparms, int algo, const char *algoelem,
kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms,
!!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION),
passphrase,
&s_key);
if (err)
{
log_error ("agent_genkey failed: %s\n", gpg_strerror (err) );
return err;
}
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/*
* Generate an Elgamal key.
*/
static int
gen_elg (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
log_assert (is_ELGAMAL (algo));
if (nbits < 1024)
{
nbits = 2048;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > 4096)
{
nbits = 4096;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
/* Note that we use transient-key only if no-protection has also
been enabled. */
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))",
algo == GCRY_PK_ELG_E ? "openpgp-elg" :
algo == GCRY_PK_ELG ? "elg" : "x-oops" ,
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "pgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an DSA key
*/
static gpg_error_t
gen_dsa (unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
unsigned int qbits;
char *keyparms;
char nbitsstr[35];
char qbitsstr[35];
if (nbits < 768)
{
nbits = 2048;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
else if ( nbits > 3072 )
{
nbits = 3072;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
if( (nbits % 64) )
{
nbits = ((nbits + 63) / 64) * 64;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/* To comply with FIPS rules we round up to the next value unless in
expert mode. */
if (!opt.expert && nbits > 1024 && (nbits % 1024))
{
nbits = ((nbits + 1023) / 1024) * 1024;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/*
Figure out a q size based on the key size. FIPS 180-3 says:
L = 1024, N = 160
L = 2048, N = 224
L = 2048, N = 256
L = 3072, N = 256
2048/256 is an odd pair since there is also a 2048/224 and
3072/256. Matching sizes is not a very exact science.
We'll do 256 qbits for nbits over 2047, 224 for nbits over 1024
but less than 2048, and 160 for 1024 (DSA1).
*/
if (nbits > 2047)
qbits = 256;
else if ( nbits > 1024)
qbits = 224;
else
qbits = 160;
if (qbits != 160 )
log_info (_("WARNING: some OpenPGP programs can't"
" handle a DSA key with this digest size\n"));
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits);
keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
strlen (qbitsstr), qbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, PUBKEY_ALGO_DSA, "pqgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an ECC key
*/
static gpg_error_t
gen_ecc (int algo, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
char *keyparms;
log_assert (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH);
if (!curve || !*curve)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Map the displayed short forms of some curves to their canonical
* names. */
if (!ascii_strcasecmp (curve, "cv25519"))
curve = "Curve25519";
else if (!ascii_strcasecmp (curve, "ed25519"))
curve = "Ed25519";
/* Note that we use the "comp" flag with EdDSA to request the use of
a 0x40 compression prefix octet. */
if (algo == PUBKEY_ALGO_EDDSA)
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519"))
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
else
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))",
strlen (curve), curve,
(((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/*
* Generate an RSA key.
*/
static int
gen_rsa (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
int err;
char *keyparms;
char nbitsstr[35];
const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096);
log_assert (is_RSA(algo));
if (!nbits)
nbits = get_keysize_range (algo, NULL, NULL);
if (nbits < 1024)
{
nbits = 3072;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > maxsize)
{
nbits = maxsize;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, algo, "ne",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
xfree (keyparms);
}
return err;
}
/****************
* check valid days:
* return 0 on error or the multiplier
*/
static int
check_valid_days( const char *s )
{
if( !digitp(s) )
return 0;
for( s++; *s; s++)
if( !digitp(s) )
break;
if( !*s )
return 1;
if( s[1] )
return 0; /* e.g. "2323wc" */
if( *s == 'd' || *s == 'D' )
return 1;
if( *s == 'w' || *s == 'W' )
return 7;
if( *s == 'm' || *s == 'M' )
return 30;
if( *s == 'y' || *s == 'Y' )
return 365;
return 0;
}
static void
print_key_flags(int flags)
{
if(flags&PUBKEY_USAGE_SIG)
tty_printf("%s ",_("Sign"));
if(flags&PUBKEY_USAGE_CERT)
tty_printf("%s ",_("Certify"));
if(flags&PUBKEY_USAGE_ENC)
tty_printf("%s ",_("Encrypt"));
if(flags&PUBKEY_USAGE_AUTH)
tty_printf("%s ",_("Authenticate"));
}
/* Ask for the key flags and return them. CURRENT gives the current
* usage which should normally be given as 0. MASK gives the allowed
* flags. */
unsigned int
ask_key_flags_with_mask (int algo, int subkey, unsigned int current,
unsigned int mask)
{
/* TRANSLATORS: Please use only plain ASCII characters for the
* translation. If this is not possible use single digits. The
* string needs to 8 bytes long. Here is a description of the
* functions:
*
* s = Toggle signing capability
* e = Toggle encryption capability
* a = Toggle authentication capability
* q = Finish
*/
const char *togglers = _("SsEeAaQq");
char *answer = NULL;
const char *s;
unsigned int possible;
if ( strlen(togglers) != 8 )
{
tty_printf ("NOTE: Bad translation at %s:%d. "
"Please report.\n", __FILE__, __LINE__);
togglers = "11223300";
}
/* Mask the possible usage flags. This is for example used for a
* card based key. */
possible = (openpgp_pk_algo_usage (algo) & mask);
/* However, only primary keys may certify. */
if (subkey)
possible &= ~PUBKEY_USAGE_CERT;
/* Preload the current set with the possible set, without
* authentication if CURRENT is 0. If CURRENT is non-zero we mask
* with all possible usages. */
if (current)
current &= possible;
else
current = (possible&~PUBKEY_USAGE_AUTH);
for (;;)
{
tty_printf("\n");
tty_printf(_("Possible actions for this %s key: "),
(algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
? "ECDSA/EdDSA" : openpgp_pk_algo_name (algo));
print_key_flags(possible);
tty_printf("\n");
tty_printf(_("Current allowed actions: "));
print_key_flags(current);
tty_printf("\n\n");
if(possible&PUBKEY_USAGE_SIG)
tty_printf(_(" (%c) Toggle the sign capability\n"),
togglers[0]);
if(possible&PUBKEY_USAGE_ENC)
tty_printf(_(" (%c) Toggle the encrypt capability\n"),
togglers[2]);
if(possible&PUBKEY_USAGE_AUTH)
tty_printf(_(" (%c) Toggle the authenticate capability\n"),
togglers[4]);
tty_printf(_(" (%c) Finished\n"),togglers[6]);
tty_printf("\n");
xfree(answer);
answer = cpr_get("keygen.flags",_("Your selection? "));
cpr_kill_prompt();
if (*answer == '=')
{
/* Hack to allow direct entry of the capabilities. */
current = 0;
for (s=answer+1; *s; s++)
{
if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG))
current |= PUBKEY_USAGE_SIG;
else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC))
current |= PUBKEY_USAGE_ENC;
else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH))
current |= PUBKEY_USAGE_AUTH;
else if (!subkey && *s == 'c')
{
/* Accept 'c' for the primary key because USAGE_CERT
will be set anyway. This is for folks who
want to experiment with a cert-only primary key. */
current |= PUBKEY_USAGE_CERT;
}
}
break;
}
else if (strlen(answer)>1)
tty_printf(_("Invalid selection.\n"));
else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7])
break;
else if((*answer==togglers[0] || *answer==togglers[1])
&& possible&PUBKEY_USAGE_SIG)
{
if(current&PUBKEY_USAGE_SIG)
current&=~PUBKEY_USAGE_SIG;
else
current|=PUBKEY_USAGE_SIG;
}
else if((*answer==togglers[2] || *answer==togglers[3])
&& possible&PUBKEY_USAGE_ENC)
{
if(current&PUBKEY_USAGE_ENC)
current&=~PUBKEY_USAGE_ENC;
else
current|=PUBKEY_USAGE_ENC;
}
else if((*answer==togglers[4] || *answer==togglers[5])
&& possible&PUBKEY_USAGE_AUTH)
{
if(current&PUBKEY_USAGE_AUTH)
current&=~PUBKEY_USAGE_AUTH;
else
current|=PUBKEY_USAGE_AUTH;
}
else
tty_printf(_("Invalid selection.\n"));
}
xfree(answer);
return current;
}
unsigned int
ask_key_flags (int algo, int subkey, unsigned int current)
{
return ask_key_flags_with_mask (algo, subkey, current, ~0);
}
/* Check whether we have a key for the key with HEXGRIP. Returns 0 if
there is no such key or the OpenPGP algo number for the key. */
static int
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
unsigned char *public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return 0;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
return map_gcry_pk_to_openpgp (algo);
}
/* Ask for an algorithm. The function returns the algorithm id to
* create. If ADDMODE is false the function won't show an option to
* create the primary and subkey combined and won't set R_USAGE
* either. If a combined algorithm has been selected, the subkey
* algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the
* user has the choice to enter the keygrip of an existing key. That
* keygrip is then stored at this address. The caller needs to free
* it. If R_CARDKEY is not NULL and the keygrip has been taken from
* an active card, true is stored there; if R_KEYTIME is not NULL the
* cretion time of that key is then stored there. */
static int
ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
char **r_keygrip, int *r_cardkey, u32 *r_keytime)
{
gpg_error_t err;
char *keygrip = NULL;
u32 keytime = 0;
char *answer = NULL;
int cardkey = 0;
int algo;
int dummy_algo;
if (!r_subkey_algo)
r_subkey_algo = &dummy_algo;
tty_printf (_("Please select what kind of key you want:\n"));
#if GPG_USE_RSA
if (!addmode)
tty_printf (_(" (%d) RSA and RSA (default)\n"), 1 );
#endif
if (!addmode && opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA and Elgamal\n"), 2 );
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (sign only)\n"), 3 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (sign only)\n"), 4 );
#endif
if (addmode)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) Elgamal (encrypt only)\n"), 5 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (encrypt only)\n"), 6 );
#endif
}
if (opt.expert)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (set your own capabilities)\n"), 7 );
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (set your own capabilities)\n"), 8 );
#endif
}
#if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA
if (opt.expert && !addmode)
tty_printf (_(" (%d) ECC and ECC\n"), 9 );
if (opt.expert)
tty_printf (_(" (%d) ECC (sign only)\n"), 10 );
if (opt.expert)
tty_printf (_(" (%d) ECC (set your own capabilities)\n"), 11 );
if (opt.expert && addmode)
tty_printf (_(" (%d) ECC (encrypt only)\n"), 12 );
#endif
if (opt.expert && r_keygrip)
tty_printf (_(" (%d) Existing key\n"), 13 );
if (r_keygrip)
tty_printf (_(" (%d) Existing key from card\n"), 14 );
for (;;)
{
*r_usage = 0;
*r_subkey_algo = 0;
xfree (answer);
answer = cpr_get ("keygen.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 1;
if (opt.compliance == CO_DE_VS
&& (algo == 2 || algo == 3 || algo == 5 || algo == 7))
{
tty_printf (_("Invalid selection.\n"));
}
else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_subkey_algo = PUBKEY_ALGO_RSA;
break;
}
else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode)
{
algo = PUBKEY_ALGO_DSA;
*r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E;
break;
}
else if (algo == 3 || !strcmp (answer, "dsa"))
{
algo = PUBKEY_ALGO_DSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if (algo == 4 || !strcmp (answer, "rsa/s"))
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 5 || !strcmp (answer, "elg")) && addmode)
{
algo = PUBKEY_ALGO_ELGAMAL_E;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_DSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 9 || !strcmp (answer, "ecc+ecc"))
&& opt.expert && !addmode)
{
algo = PUBKEY_ALGO_ECDSA;
*r_subkey_algo = PUBKEY_ALGO_ECDH;
break;
}
else if ((algo == 10 || !strcmp (answer, "ecc/s")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 12 || !strcmp (answer, "ecc/e"))
&& opt.expert && addmode)
{
algo = PUBKEY_ALGO_ECDH;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 13 || !strcmp (answer, "keygrip"))
&& opt.expert && r_keygrip)
{
for (;;)
{
xfree (answer);
answer = tty_get (_("Enter the keygrip: "));
tty_kill_prompt ();
trim_spaces (answer);
if (!*answer)
{
xfree (answer);
answer = NULL;
continue;
}
if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf
(_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(algo = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip)
{
char *serialno;
keypair_info_t keypairlist, kpi;
int count, selection;
err = agent_scd_serialno (&serialno, NULL);
if (err)
{
tty_printf (_("error reading the card: %s\n"),
gpg_strerror (err));
goto ask_again;
}
tty_printf (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
err = agent_scd_keypairinfo (ctrl, NULL, &keypairlist);
if (err)
{
tty_printf (_("error reading the card: %s\n"),
gpg_strerror (err));
goto ask_again;
}
do
{
char *authkeyref, *encrkeyref, *signkeyref;
agent_scd_getattr_one ("$AUTHKEYID", &authkeyref);
agent_scd_getattr_one ("$ENCRKEYID", &encrkeyref);
agent_scd_getattr_one ("$SIGNKEYID", &signkeyref);
tty_printf (_("Available keys:\n"));
for (count=1, kpi=keypairlist; kpi; kpi = kpi->next, count++)
{
gcry_sexp_t s_pkey;
char *algostr = NULL;
enum gcry_pk_algos algoid = 0;
const char *keyref = kpi->idstr;
int any = 0;
if (keyref
&& !agent_scd_readkey (ctrl, keyref, &s_pkey, NULL))
{
algostr = pubkey_algo_string (s_pkey, &algoid);
gcry_sexp_release (s_pkey);
}
/* We need to tweak the algo in case
* GCRY_PK_ECC is returned because pubkey_algo_string
* is not aware of the OpenPGP algo mapping.
* FIXME: This is an ugly hack. */
if (algoid == GCRY_PK_ECC
&& algostr && !strncmp (algostr, "nistp", 5)
&& !(kpi->usage & GCRY_PK_USAGE_ENCR))
kpi->algo = PUBKEY_ALGO_ECDSA;
else if (algoid == GCRY_PK_ECC
&& algostr && !strcmp (algostr, "ed25519")
&& !(kpi->usage & GCRY_PK_USAGE_ENCR))
kpi->algo = PUBKEY_ALGO_EDDSA;
else
kpi->algo = map_gcry_pk_to_openpgp (algoid);
tty_printf (" (%d) %s %s %s",
count, kpi->keygrip, keyref, algostr);
if ((kpi->usage & GCRY_PK_USAGE_CERT))
{
tty_printf ("%scert", any?",":" (");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_SIGN))
{
tty_printf ("%ssign%s", any?",":" (",
(signkeyref && keyref
&& !strcmp (signkeyref, keyref))? "*":"");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_AUTH))
{
tty_printf ("%sauth%s", any?",":" (",
(authkeyref && keyref
&& !strcmp (authkeyref, keyref))? "*":"");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_ENCR))
{
tty_printf ("%sencr%s", any?",":" (",
(encrkeyref && keyref
&& !strcmp (encrkeyref, keyref))? "*":"");
any = 1;
}
tty_printf ("%s\n", any?")":"");
xfree (algostr);
}
xfree (answer);
answer = cpr_get ("keygen.cardkey", _("Your selection? "));
cpr_kill_prompt ();
trim_spaces (answer);
selection = atoi (answer);
xfree (authkeyref);
xfree (encrkeyref);
xfree (signkeyref);
}
while (!(selection > 0 && selection < count));
for (count=1,kpi=keypairlist; kpi; kpi = kpi->next, count++)
if (count == selection)
break;
if (!kpi)
{
/* Just in case COUNT is zero (no keys). */
free_keypair_info (keypairlist);
goto ask_again;
}
xfree (keygrip);
keygrip = xstrdup (kpi->keygrip);
cardkey = 1;
algo = kpi->algo;
keytime = kpi->keytime;
/* In expert mode allow to change the usage flags. */
if (opt.expert)
*r_usage = ask_key_flags_with_mask (algo, addmode,
kpi->usage, kpi->usage);
else
{
*r_usage = kpi->usage;
if (addmode)
*r_usage &= ~GCRY_PK_USAGE_CERT;
}
free_keypair_info (keypairlist);
break;
}
else
tty_printf (_("Invalid selection.\n"));
ask_again:
;
}
xfree(answer);
if (r_keygrip)
*r_keygrip = keygrip;
if (r_cardkey)
*r_cardkey = cardkey;
if (r_keytime)
*r_keytime = keytime;
return algo;
}
static unsigned int
get_keysize_range (int algo, unsigned int *min, unsigned int *max)
{
unsigned int def;
unsigned int dummy1, dummy2;
if (!min)
min = &dummy1;
if (!max)
max = &dummy2;
switch(algo)
{
case PUBKEY_ALGO_DSA:
*min = opt.expert? 768 : 1024;
*max=3072;
def=2048;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
*min=256;
*max=521;
def=256;
break;
case PUBKEY_ALGO_EDDSA:
*min=255;
*max=441;
def=255;
break;
default:
*min = opt.compliance == CO_DE_VS ? 2048: 1024;
*max = 4096;
def = 3072;
break;
}
return def;
}
/* Return a fixed up keysize depending on ALGO. */
static unsigned int
fixup_keysize (unsigned int nbits, int algo, int silent)
{
if (algo == PUBKEY_ALGO_DSA && (nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits);
}
else if (algo == PUBKEY_ALGO_EDDSA)
{
if (nbits != 255 && nbits != 441)
{
if (nbits < 256)
nbits = 255;
else
nbits = 441;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
{
if (nbits != 256 && nbits != 384 && nbits != 521)
{
if (nbits < 256)
nbits = 256;
else if (nbits < 384)
nbits = 384;
else
nbits = 521;
if (!silent)
tty_printf (_("rounded to %u bits\n"), nbits);
}
}
else if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
if (!silent)
tty_printf (_("rounded up to %u bits\n"), nbits );
}
return nbits;
}
/* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE
is not 0, the function asks for the size of the encryption
subkey. */
static unsigned
ask_keysize (int algo, unsigned int primary_keysize)
{
unsigned int nbits;
unsigned int min, def, max;
int for_subkey = !!primary_keysize;
int autocomp = 0;
def = get_keysize_range (algo, &min, &max);
if (primary_keysize && !opt.expert)
{
/* Deduce the subkey size from the primary key size. */
if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072)
nbits = 3072; /* For performance reasons we don't support more
than 3072 bit DSA. However we won't see this
case anyway because DSA can't be used as an
encryption subkey ;-). */
else
nbits = primary_keysize;
autocomp = 1;
goto leave;
}
tty_printf(_("%s keys may be between %u and %u bits long.\n"),
openpgp_pk_algo_name (algo), min, max);
for (;;)
{
char *prompt, *answer;
if (for_subkey)
prompt = xasprintf (_("What keysize do you want "
"for the subkey? (%u) "), def);
else
prompt = xasprintf (_("What keysize do you want? (%u) "), def);
answer = cpr_get ("keygen.size", prompt);
cpr_kill_prompt ();
nbits = *answer? atoi (answer): def;
xfree(prompt);
xfree(answer);
if(nbits<min || nbits>max)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
openpgp_pk_algo_name (algo), min, max);
else
break;
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
leave:
nbits = fixup_keysize (nbits, algo, autocomp);
return nbits;
}
/* Ask for the curve. ALGO is the selected algorithm which this
function may adjust. Returns a const string of the name of the
curve. */
const char *
ask_curve (int *algo, int *subkey_algo, const char *current)
{
/* NB: We always use a complete algo list so that we have stable
numbers in the menu regardless on how Gpg was configured. */
struct {
const char *name;
const char* eddsa_curve; /* Corresponding EdDSA curve. */
const char *pretty_name;
unsigned int supported : 1; /* Supported by gpg. */
unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */
unsigned int expert_only : 1; /* Only with --expert */
unsigned int available : 1; /* Available in Libycrypt (runtime checked) */
} curves[] = {
#if GPG_USE_ECDSA || GPG_USE_ECDH
# define MY_USE_ECDSADH 1
#else
# define MY_USE_ECDSADH 0
#endif
{ "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 },
{ "Curve448", "Ed448", "Curve 448", 0/*reserved*/ , 0, 1, 0 },
{ "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 },
{ "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 },
{ "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
};
#undef MY_USE_ECDSADH
int idx;
char *answer;
const char *result = NULL;
gcry_sexp_t keyparms;
tty_printf (_("Please select which elliptic curve you want:\n"));
keyparms = NULL;
for (idx=0; idx < DIM(curves); idx++)
{
int rc;
curves[idx].available = 0;
if (!curves[idx].supported)
continue;
if (opt.compliance==CO_DE_VS)
{
if (!curves[idx].de_vs)
continue; /* Not allowed. */
}
else if (!opt.expert && curves[idx].expert_only)
continue;
/* We need to switch from the ECDH name of the curve to the
EDDSA name of the curve if we want a signing key. */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].eddsa_curve? curves[idx].eddsa_curve
/**/ : curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
if (subkey_algo && curves[idx].eddsa_curve)
{
/* Both Curve 25519 (or 448) keys are to be created. Check that
Libgcrypt also supports the real Curve25519 (or 448). */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
}
curves[idx].available = 1;
tty_printf (" (%d) %s\n", idx + 1,
curves[idx].pretty_name?
curves[idx].pretty_name:curves[idx].name);
}
gcry_sexp_release (keyparms);
for (;;)
{
answer = cpr_get ("keygen.curve", _("Your selection? "));
cpr_kill_prompt ();
idx = *answer? atoi (answer) : 1;
if (!*answer && current)
{
xfree(answer);
return NULL;
}
else if (*answer && !idx)
{
/* See whether the user entered the name of the curve. */
for (idx=0; idx < DIM(curves); idx++)
{
if (!opt.expert && curves[idx].expert_only)
continue;
if (!stricmp (curves[idx].name, answer)
|| (curves[idx].pretty_name
&& !stricmp (curves[idx].pretty_name, answer)))
break;
}
if (idx == DIM(curves))
idx = -1;
}
else
idx--;
xfree(answer);
answer = NULL;
if (idx < 0 || idx >= DIM (curves) || !curves[idx].available)
tty_printf (_("Invalid selection.\n"));
else
{
/* If the user selected a signing algorithm and Curve25519
we need to set the algo to EdDSA and update the curve name.
If switching away from EdDSA, we need to set the algo back
to ECDSA. */
if (*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA)
{
if (curves[idx].eddsa_curve)
{
if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA)
*subkey_algo = PUBKEY_ALGO_EDDSA;
*algo = PUBKEY_ALGO_EDDSA;
result = curves[idx].eddsa_curve;
}
else
{
if (subkey_algo && *subkey_algo == PUBKEY_ALGO_EDDSA)
*subkey_algo = PUBKEY_ALGO_ECDSA;
*algo = PUBKEY_ALGO_ECDSA;
result = curves[idx].name;
}
}
else
result = curves[idx].name;
break;
}
}
if (!result)
result = curves[0].name;
return result;
}
/****************
* Parse an expire string and return its value in seconds.
* Returns (u32)-1 on error.
* This isn't perfect since scan_isodatestr returns unix time, and
* OpenPGP actually allows a 32-bit time *plus* a 32-bit offset.
* Because of this, we only permit setting expirations up to 2106, but
* OpenPGP could theoretically allow up to 2242. I think we'll all
* just cope for the next few years until we get a 64-bit time_t or
* similar.
*/
u32
parse_expire_string( const char *string )
{
int mult;
u32 seconds;
u32 abs_date = 0;
u32 curtime = make_timestamp ();
time_t tt;
if (!string || !*string || !strcmp (string, "none")
|| !strcmp (string, "never") || !strcmp (string, "-"))
seconds = 0;
else if (!strncmp (string, "seconds=", 8))
seconds = atoi (string+8);
else if ((abs_date = scan_isodatestr(string))
&& (abs_date+86400/2) > curtime)
seconds = (abs_date+86400/2) - curtime;
else if ((tt = isotime2epoch (string)) != (time_t)(-1))
seconds = (u32)tt - curtime;
else if ((mult = check_valid_days (string)))
seconds = atoi (string) * 86400L * mult;
else
seconds = (u32)(-1);
return seconds;
}
/* Parse a Creation-Date string which is either "1986-04-26" or
"19860426T042640". Returns 0 on error. */
static u32
parse_creation_string (const char *string)
{
u32 seconds;
if (!*string)
seconds = 0;
else if ( !strncmp (string, "seconds=", 8) )
seconds = atoi (string+8);
else if ( !(seconds = scan_isodatestr (string)))
{
time_t tmp = isotime2epoch (string);
seconds = (tmp == (time_t)(-1))? 0 : tmp;
}
return seconds;
}
/* object == 0 for a key, and 1 for a sig */
u32
ask_expire_interval(int object,const char *def_expire)
{
u32 interval;
char *answer;
switch(object)
{
case 0:
if(def_expire)
BUG();
tty_printf(_("Please specify how long the key should be valid.\n"
" 0 = key does not expire\n"
" <n> = key expires in n days\n"
" <n>w = key expires in n weeks\n"
" <n>m = key expires in n months\n"
" <n>y = key expires in n years\n"));
break;
case 1:
if(!def_expire)
BUG();
tty_printf(_("Please specify how long the signature should be valid.\n"
" 0 = signature does not expire\n"
" <n> = signature expires in n days\n"
" <n>w = signature expires in n weeks\n"
" <n>m = signature expires in n months\n"
" <n>y = signature expires in n years\n"));
break;
default:
BUG();
}
/* Note: The elgamal subkey for DSA has no expiration date because
* it must be signed with the DSA key and this one has the expiration
* date */
answer = NULL;
for(;;)
{
u32 curtime;
xfree(answer);
if(object==0)
answer = cpr_get("keygen.valid",_("Key is valid for? (0) "));
else
{
char *prompt;
prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire);
answer = cpr_get("siggen.valid",prompt);
xfree(prompt);
if(*answer=='\0')
answer=xstrdup(def_expire);
}
cpr_kill_prompt();
trim_spaces(answer);
curtime = make_timestamp ();
interval = parse_expire_string( answer );
if( interval == (u32)-1 )
{
tty_printf(_("invalid value\n"));
continue;
}
if( !interval )
{
tty_printf((object==0)
? _("Key does not expire at all\n")
: _("Signature does not expire at all\n"));
}
else
{
tty_printf(object==0
? _("Key expires at %s\n")
: _("Signature expires at %s\n"),
asctimestamp((ulong)(curtime + interval) ) );
#if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T)
if ( (time_t)((ulong)(curtime+interval)) < 0 )
tty_printf (_("Your system can't display dates beyond 2038.\n"
"However, it will be correctly handled up to"
" 2106.\n"));
else
#endif /*SIZEOF_TIME_T*/
if ( (time_t)((unsigned long)(curtime+interval)) < curtime )
{
tty_printf (_("invalid value\n"));
continue;
}
}
if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay",
_("Is this correct? (y/N) ")) )
break;
}
xfree(answer);
return interval;
}
u32
ask_expiredate()
{
u32 x = ask_expire_interval(0,NULL);
return x? make_timestamp() + x : 0;
}
static PKT_user_id *
uid_from_string (const char *string)
{
size_t n;
PKT_user_id *uid;
n = strlen (string);
uid = xmalloc_clear (sizeof *uid + n);
uid->len = n;
strcpy (uid->name, string);
uid->ref = 1;
return uid;
}
/* Return true if the user id UID already exists in the keyblock. */
static int
uid_already_in_keyblock (kbnode_t keyblock, const char *uid)
{
PKT_user_id *uidpkt = uid_from_string (uid);
kbnode_t node;
int result = 0;
for (node=keyblock; node && !result; node=node->next)
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids (uidpkt, node->pkt->pkt.user_id))
result = 1;
free_user_id (uidpkt);
return result;
}
/* Ask for a user ID. With a MODE of 1 an extra help prompt is
printed for use during a new key creation. If KEYBLOCK is not NULL
the function prevents the creation of an already existing user
ID. IF FULL is not set some prompts are not shown. */
static char *
ask_user_id (int mode, int full, KBNODE keyblock)
{
char *answer;
char *aname, *acomment, *amail, *uid;
if ( !mode )
{
/* TRANSLATORS: This is the new string telling the user what
gpg is now going to do (i.e. ask for the parts of the user
ID). Note that if you do not translate this string, a
different string will be used, which might still have
a correct translation. */
const char *s1 =
N_("\n"
"GnuPG needs to construct a user ID to identify your key.\n"
"\n");
const char *s2 = _(s1);
if (!strcmp (s1, s2))
{
/* There is no translation for the string thus we to use
the old info text. gettext has no way to tell whether
a translation is actually available, thus we need to
to compare again. */
/* TRANSLATORS: This string is in general not anymore used
but you should keep your existing translation. In case
the new string is not translated this old string will
be used. */
const char *s3 = N_("\n"
"You need a user ID to identify your key; "
"the software constructs the user ID\n"
"from the Real Name, Comment and Email Address in this form:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n\n");
const char *s4 = _(s3);
if (strcmp (s3, s4))
s2 = s3; /* A translation exists - use it. */
}
tty_printf ("%s", s2) ;
}
uid = aname = acomment = amail = NULL;
for(;;) {
char *p;
int fail=0;
if( !aname ) {
for(;;) {
xfree(aname);
aname = cpr_get("keygen.name",_("Real name: "));
trim_spaces(aname);
cpr_kill_prompt();
if( opt.allow_freeform_uid )
break;
if( strpbrk( aname, "<>" ) )
{
tty_printf(_("Invalid character in name\n"));
tty_printf(_("The characters '%s' and '%s' may not "
"appear in name\n"), "<", ">");
}
else if( digitp(aname) )
tty_printf(_("Name may not start with a digit\n"));
else if (*aname && strlen (aname) < 5)
{
tty_printf(_("Name must be at least 5 characters long\n"));
/* However, we allow an empty name. */
}
else
break;
}
}
if( !amail ) {
for(;;) {
xfree(amail);
amail = cpr_get("keygen.email",_("Email address: "));
trim_spaces(amail);
cpr_kill_prompt();
if( !*amail || opt.allow_freeform_uid )
break; /* no email address is okay */
else if ( !is_valid_mailbox (amail) )
tty_printf(_("Not a valid email address\n"));
else
break;
}
}
if (!acomment) {
if (full) {
for(;;) {
xfree(acomment);
acomment = cpr_get("keygen.comment",_("Comment: "));
trim_spaces(acomment);
cpr_kill_prompt();
if( !*acomment )
break; /* no comment is okay */
else if( strpbrk( acomment, "()" ) )
tty_printf(_("Invalid character in comment\n"));
else
break;
}
}
else {
xfree (acomment);
acomment = xstrdup ("");
}
}
xfree(uid);
uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10);
if (!*aname && *amail && !*acomment && !random_is_faked ())
{ /* Empty name and comment but with mail address. Use
simplified form with only the non-angle-bracketed mail
address. */
p = stpcpy (p, amail);
}
else
{
p = stpcpy (p, aname );
if (*acomment)
p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")");
if (*amail)
p = stpcpy(stpcpy(stpcpy(p," <"), amail),">");
}
/* Append a warning if the RNG is switched into fake mode. */
if ( random_is_faked () )
strcpy(p, " (insecure!)" );
/* print a note in case that UTF8 mapping has to be done */
for(p=uid; *p; p++ ) {
if( *p & 0x80 ) {
tty_printf(_("You are using the '%s' character set.\n"),
get_native_charset() );
break;
}
}
tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid);
if( !*amail && !opt.allow_freeform_uid
&& (strchr( aname, '@' ) || strchr( acomment, '@'))) {
fail = 1;
tty_printf(_("Please don't put the email address "
"into the real name or the comment\n") );
}
if (!fail && keyblock)
{
if (uid_already_in_keyblock (keyblock, uid))
{
tty_printf (_("Such a user ID already exists on this key!\n"));
fail = 1;
}
}
for(;;) {
/* TRANSLATORS: These are the allowed answers in
lower and uppercase. Below you will find the matching
string which should be translated accordingly and the
letter changed to match the one in the answer string.
n = Change name
c = Change comment
e = Change email
o = Okay (ready, continue)
q = Quit
*/
const char *ansstr = _("NnCcEeOoQq");
if( strlen(ansstr) != 10 )
BUG();
if( cpr_enabled() ) {
answer = xstrdup (ansstr + (fail?8:6));
answer[1] = 0;
}
else if (full) {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") :
_("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
else {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (E)mail, or (Q)uit? ") :
_("Change (N)ame, (E)mail, or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
if( strlen(answer) > 1 )
;
else if( *answer == ansstr[0] || *answer == ansstr[1] ) {
xfree(aname); aname = NULL;
break;
}
else if( *answer == ansstr[2] || *answer == ansstr[3] ) {
xfree(acomment); acomment = NULL;
break;
}
else if( *answer == ansstr[4] || *answer == ansstr[5] ) {
xfree(amail); amail = NULL;
break;
}
else if( *answer == ansstr[6] || *answer == ansstr[7] ) {
if( fail ) {
tty_printf(_("Please correct the error first\n"));
}
else {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
break;
}
}
else if( *answer == ansstr[8] || *answer == ansstr[9] ) {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
xfree(uid); uid = NULL;
break;
}
xfree(answer);
}
xfree(answer);
if (!amail && !acomment)
break;
xfree(uid); uid = NULL;
}
if( uid ) {
char *p = native_to_utf8( uid );
xfree( uid );
uid = p;
}
return uid;
}
/* Basic key generation. Here we divert to the actual generation
routines based on the requested algorithm. */
static int
do_create (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expiredate, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
/* Fixme: The entropy collecting message should be moved to a
libgcrypt progress handler. */
if (!opt.batch)
tty_printf (_(
"We need to generate a lot of random bytes. It is a good idea to perform\n"
"some other action (type on the keyboard, move the mouse, utilize the\n"
"disks) during the prime generation; this gives the random number\n"
"generator a better chance to gain enough entropy.\n") );
if (algo == PUBKEY_ALGO_ELGAMAL_E)
err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_DSA)
err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else if (algo == PUBKEY_ALGO_RSA)
err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr);
else
BUG();
return err;
}
/* Generate a new user id packet or return NULL if canceled. If
KEYBLOCK is not NULL the function prevents the creation of an
already existing user ID. If UIDSTR is not NULL the user is not
asked but UIDSTR is used to create the user id packet; if the user
id already exists NULL is returned. UIDSTR is expected to be utf-8
encoded and should have already been checked for a valid length
etc. */
PKT_user_id *
generate_user_id (KBNODE keyblock, const char *uidstr)
{
PKT_user_id *uid;
char *p;
if (uidstr)
{
if (uid_already_in_keyblock (keyblock, uidstr))
return NULL; /* Already exists. */
uid = uid_from_string (uidstr);
}
else
{
p = ask_user_id (1, 1, keyblock);
if (!p)
return NULL; /* Canceled. */
uid = uid_from_string (p);
xfree (p);
}
return uid;
}
/* Helper for parse_key_parameter_part_parameter_string for one part of the
* specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty
* success is returned. On error an error code is returned. Note
* that STRING may be modified by this function. NULL may be passed
* for any parameter. FOR_SUBKEY shall be true if this is used as a
* subkey. If CLEAR_CERT is set a default CERT usage will be cleared;
* this is useful if for example the default algorithm is used for a
* subkey. If R_KEYVERSION is not NULL it will receive the version of
* the key; this is currently 4 but can be changed with the flag "v5"
* to create a v5 key. If R_KEYTIME is not NULL and the key has been
- * taken fron active OpenPGP card, its creation time is stored
+ * taken from active OpenPGP card, its creation time is stored
* there. */
static gpg_error_t
parse_key_parameter_part (ctrl_t ctrl,
char *string, int for_subkey, int clear_cert,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve, int *r_keyversion,
char **r_keygrip, u32 *r_keytime)
{
gpg_error_t err;
char *flags;
int algo;
char *endp;
const char *curve = NULL;
int ecdh_or_ecdsa = 0;
unsigned int size;
int keyuse;
int keyversion = 4;
int i;
const char *s;
int from_card = 0;
char *keygrip = NULL;
u32 keytime = 0;
if (!string || !*string)
return 0; /* Success. */
flags = strchr (string, '/');
if (flags)
*flags++ = 0;
algo = 0;
if (!ascii_strcasecmp (string, "card"))
from_card = 1;
else if (strlen (string) >= 3 && (digitp (string+3) || !string[3]))
{
if (!ascii_memcasecmp (string, "rsa", 3))
algo = PUBKEY_ALGO_RSA;
else if (!ascii_memcasecmp (string, "dsa", 3))
algo = PUBKEY_ALGO_DSA;
else if (!ascii_memcasecmp (string, "elg", 3))
algo = PUBKEY_ALGO_ELGAMAL_E;
}
if (from_card)
; /* We need the flags before we can figure out the key to use. */
else if (algo)
{
if (!string[3])
size = get_keysize_range (algo, NULL, NULL);
else
{
size = strtoul (string+3, &endp, 10);
if (size < 512 || size > 16384 || *endp)
return gpg_error (GPG_ERR_INV_VALUE);
}
}
else if ((curve = openpgp_is_curve_supported (string, &algo, &size)))
{
if (!algo)
{
algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */
ecdh_or_ecdsa = 1; /* We may need to switch the algo. */
}
}
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Parse the flags. */
keyuse = 0;
if (flags)
{
char **tokens = NULL;
tokens = strtokenize (flags, ",");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
keyuse |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
keyuse |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
keyuse |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
keyuse |= PUBKEY_USAGE_CERT;
else if (!ascii_strcasecmp (s, "ecdsa") && !from_card)
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDSA;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "ecdh") && !from_card)
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDH;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "eddsa") && !from_card)
{
/* Not required but we allow it for consistency. */
if (algo == PUBKEY_ALGO_EDDSA)
;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
}
else if (!ascii_strcasecmp (s, "v5"))
{
if (opt.flags.rfc4880bis)
keyversion = 5;
}
else if (!ascii_strcasecmp (s, "v4"))
keyversion = 4;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_UNKNOWN_FLAG);
}
}
xfree (tokens);
}
/* If not yet decided switch between ecdh and ecdsa unless we want
* to read the algo from the current card. */
if (from_card)
{
keypair_info_t keypairlist, kpi;
char *reqkeyref;
if (!keyuse)
keyuse = (for_subkey? PUBKEY_USAGE_ENC
/* */ : (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG));
/* Access the card to make sure we have one and to show the S/N. */
{
char *serialno;
err = agent_scd_serialno (&serialno, NULL);
if (err)
{
log_error (_("error reading the card: %s\n"), gpg_strerror (err));
return err;
}
if (!opt.quiet)
log_info (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
}
err = agent_scd_keypairinfo (ctrl, NULL, &keypairlist);
if (err)
{
log_error (_("error reading the card: %s\n"), gpg_strerror (err));
return err;
}
agent_scd_getattr_one ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT))
? "$SIGNKEYID":"$ENCRKEYID", &reqkeyref);
algo = 0; /* Should already be the case. */
for (kpi=keypairlist; kpi && !algo; kpi = kpi->next)
{
gcry_sexp_t s_pkey;
char *algostr = NULL;
enum gcry_pk_algos algoid = 0;
const char *keyref = kpi->idstr;
if (!reqkeyref)
continue; /* Card does not provide the info (skip all). */
if (!keyref)
continue; /* Ooops. */
if (strcmp (reqkeyref, keyref))
continue; /* This is not the requested keyref. */
if ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT))
&& (kpi->usage & (GCRY_PK_USAGE_SIGN|GCRY_PK_USAGE_CERT)))
; /* Okay */
else if ((keyuse & PUBKEY_USAGE_ENC)
&& (kpi->usage & GCRY_PK_USAGE_ENCR))
; /* Okay */
else
continue; /* Not usable for us. */
if (agent_scd_readkey (ctrl, keyref, &s_pkey, NULL))
continue; /* Could not read the key. */
algostr = pubkey_algo_string (s_pkey, &algoid);
gcry_sexp_release (s_pkey);
/* Map to OpenPGP algo number.
* We need to tweak the algo in case GCRY_PK_ECC is returned
* because pubkey_algo_string is not aware of the OpenPGP
* algo mapping. FIXME: This is an ugly hack. */
if (algoid == GCRY_PK_ECC
&& algostr && !strncmp (algostr, "nistp", 5)
&& !(kpi->usage & GCRY_PK_USAGE_ENCR))
algo = PUBKEY_ALGO_ECDSA;
else if (algoid == GCRY_PK_ECC
&& algostr && !strcmp (algostr, "ed25519")
&& !(kpi->usage & GCRY_PK_USAGE_ENCR))
algo = PUBKEY_ALGO_EDDSA;
else
algo = map_gcry_pk_to_openpgp (algoid);
xfree (algostr);
xfree (keygrip);
keygrip = xtrystrdup (kpi->keygrip);
if (!keygrip)
{
err = gpg_error_from_syserror ();
xfree (reqkeyref);
free_keypair_info (keypairlist);
return err;
}
keytime = kpi->keytime;
}
xfree (reqkeyref);
free_keypair_info (keypairlist);
if (!algo || !keygrip)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("no usable key on the card: %s\n", gpg_strerror (err));
xfree (keygrip);
return err;
}
}
else if (ecdh_or_ecdsa && keyuse)
algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
else if (ecdh_or_ecdsa)
algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
/* Set or fix key usage. */
if (!keyuse)
{
if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
keyuse = PUBKEY_USAGE_SIG;
else if (algo == PUBKEY_ALGO_RSA)
keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG;
else
keyuse = PUBKEY_USAGE_ENC;
}
else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
{
keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E)
{
keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */
}
/* Make sure a primary key can certify. */
if (!for_subkey)
keyuse |= PUBKEY_USAGE_CERT;
/* But if requested remove th cert usage. */
if (clear_cert)
keyuse &= ~PUBKEY_USAGE_CERT;
/* Check that usage is actually possible. */
if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((keyuse & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (keyuse & PUBKEY_USAGE_CERT)))
{
xfree (keygrip);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Return values. */
if (r_algo)
*r_algo = algo;
if (r_size)
{
unsigned int min, def, max;
/* Make sure the keysize is in the allowed range. */
def = get_keysize_range (algo, &min, &max);
if (!size)
size = def;
else if (size < min)
size = min;
else if (size > max)
size = max;
*r_size = fixup_keysize (size, algo, 1);
}
if (r_keyuse)
*r_keyuse = keyuse;
if (r_curve)
*r_curve = curve;
if (r_keyversion)
*r_keyversion = keyversion;
if (r_keygrip)
*r_keygrip = keygrip;
else
xfree (keygrip);
if (r_keytime)
*r_keytime = keytime;
return 0;
}
/* Parse and return the standard key generation parameter.
* The string is expected to be in this format:
*
* ALGO[/FLAGS][+SUBALGO[/FLAGS]]
*
* Here ALGO is a string in the same format as printed by the
* keylisting. For example:
*
* rsa3072 := RSA with 3072 bit.
* dsa2048 := DSA with 2048 bit.
* elg2048 := Elgamal with 2048 bit.
* ed25519 := EDDSA using curve Ed25519.
* cv25519 := ECDH using curve Curve25519.
* nistp256:= ECDSA or ECDH using curve NIST P-256
*
* All strings with an unknown prefix are considered an elliptic
* curve. Curves which have no implicit algorithm require that FLAGS
* is given to select whether ECDSA or ECDH is used; this can either
* be done using an algorithm keyword or usage keywords.
*
* FLAGS is a comma delimited string of keywords:
*
* cert := Allow usage Certify
* sign := Allow usage Sign
* encr := Allow usage Encrypt
* auth := Allow usage Authentication
* encrypt := Alias for "encr"
* ecdsa := Use algorithm ECDSA.
* eddsa := Use algorithm EdDSA.
* ecdh := Use algorithm ECDH.
* v5 := Create version 5 key (requires option --rfc4880bis)
*
* There are several defaults and fallbacks depending on the
* algorithm. PART can be used to select which part of STRING is
* used:
* -1 := Both parts
* 0 := Only the part of the primary key
* 1 := If there is one part parse that one, if there are
* two parts parse the part which best matches the
* SUGGESTED_USE or in case that can't be evaluated the second part.
* Always return using the args for the primary key (R_ALGO,....).
*
*/
gpg_error_t
parse_key_parameter_string (ctrl_t ctrl,
const char *string, int part,
unsigned int suggested_use,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve,
int *r_version,
char **r_keygrip,
u32 *r_keytime,
int *r_subalgo, unsigned int *r_subsize,
unsigned int *r_subkeyuse,
char const **r_subcurve,
int *r_subversion,
char **r_subkeygrip,
u32 *r_subkeytime)
{
gpg_error_t err = 0;
char *primary, *secondary;
if (r_algo)
*r_algo = 0;
if (r_size)
*r_size = 0;
if (r_keyuse)
*r_keyuse = 0;
if (r_curve)
*r_curve = NULL;
if (r_version)
*r_version = 4;
if (r_keygrip)
*r_keygrip = NULL;
if (r_keytime)
*r_keytime = 0;
if (r_subalgo)
*r_subalgo = 0;
if (r_subsize)
*r_subsize = 0;
if (r_subkeyuse)
*r_subkeyuse = 0;
if (r_subcurve)
*r_subcurve = NULL;
if (r_subversion)
*r_subversion = 4;
if (r_subkeygrip)
*r_subkeygrip = NULL;
if (r_subkeytime)
*r_subkeytime = 0;
if (!string || !*string
|| !ascii_strcasecmp (string, "default") || !strcmp (string, "-"))
string = get_default_pubkey_algo ();
else if (!ascii_strcasecmp (string, "future-default")
|| !ascii_strcasecmp (string, "futuredefault"))
string = FUTURE_STD_KEY_PARAM;
else if (!ascii_strcasecmp (string, "card"))
string = "card/cert,sign+card/encr";
primary = xstrdup (string);
secondary = strchr (primary, '+');
if (secondary)
*secondary++ = 0;
if (part == -1 || part == 0)
{
err = parse_key_parameter_part (ctrl, primary,
0, 0, r_algo, r_size,
r_keyuse, r_curve, r_version,
r_keygrip, r_keytime);
if (!err && part == -1)
err = parse_key_parameter_part (ctrl, secondary,
1, 0, r_subalgo, r_subsize,
r_subkeyuse, r_subcurve, r_subversion,
r_subkeygrip, r_subkeytime);
}
else if (part == 1)
{
/* If we have SECONDARY, use that part. If there is only one
* part consider this to be the subkey algo. In case a
* SUGGESTED_USE has been given and the usage of the secondary
* part does not match SUGGESTED_USE try again using the primary
* part. Note that when falling back to the primary key we need
* to force clearing the cert usage. */
if (secondary)
{
err = parse_key_parameter_part (ctrl, secondary,
1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse))
err = parse_key_parameter_part (ctrl, primary,
1, 1 /*(clear cert)*/,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
}
else
err = parse_key_parameter_part (ctrl, primary,
1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
}
xfree (primary);
return err;
}
/* Append R to the linked list PARA. */
static void
append_to_parameter (struct para_data_s *para, struct para_data_s *r)
{
log_assert (para);
while (para->next)
para = para->next;
para->next = r;
}
/* Release the parameter list R. */
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
if (r->key == pPASSPHRASE && *r->u.value)
wipememory (r->u.value, strlen (r->u.value));
xfree (r);
}
}
static struct para_data_s *
get_parameter( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r;
for( r = para; r && r->key != key; r = r->next )
;
return r;
}
static const char *
get_parameter_value( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return (r && *r->u.value)? r->u.value : NULL;
}
/* This is similar to get_parameter_value but also returns the empty
string. This is required so that quick_generate_keypair can use an
empty Passphrase to specify no-protection. */
static const char *
get_parameter_passphrase (struct para_data_s *para)
{
struct para_data_s *r = get_parameter (para, pPASSPHRASE);
return r ? r->u.value : NULL;
}
static int
get_parameter_algo (ctrl_t ctrl, struct para_data_s *para, enum para_name key,
int *r_default)
{
int i;
struct para_data_s *r = get_parameter( para, key );
if (r_default)
*r_default = 0;
if (!r)
return -1;
/* Note that we need to handle the ECC algorithms specified as
strings directly because Libgcrypt folds them all to ECC. */
if (!ascii_strcasecmp (r->u.value, "default"))
{
/* Note: If you change this default algo, remember to change it
* also in gpg.c:gpgconf_list. */
/* FIXME: We only allow the algo here and have a separate thing
* for the curve etc. That is a ugly but demanded for backward
* compatibility with the batch key generation. It would be
* better to make full use of parse_key_parameter_string. */
parse_key_parameter_string (ctrl, NULL, 0, 0,
&i, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (r_default)
*r_default = 1;
}
else if (digitp (r->u.value))
i = atoi( r->u.value );
else if (!strcmp (r->u.value, "ELG-E")
|| !strcmp (r->u.value, "ELG"))
i = PUBKEY_ALGO_ELGAMAL_E;
else if (!ascii_strcasecmp (r->u.value, "EdDSA"))
i = PUBKEY_ALGO_EDDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDSA"))
i = PUBKEY_ALGO_ECDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDH"))
i = PUBKEY_ALGO_ECDH;
else
i = map_gcry_pk_to_openpgp (gcry_pk_map_name (r->u.value));
if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S)
i = 0; /* we don't want to allow generation of these algorithms */
return i;
}
/* Parse a usage string. The usage keywords "auth", "sign", "encr"
* may be delimited by space, tab, or comma. On error -1 is returned
* instead of the usage flags. */
static int
parse_usagestr (const char *usagestr)
{
gpg_error_t err;
char **tokens = NULL;
const char *s;
int i;
unsigned int use = 0;
tokens = strtokenize (usagestr, " \t,");
if (!tokens)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
return -1;
}
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
use |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
use |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
use |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
use |= PUBKEY_USAGE_CERT;
else
{
xfree (tokens);
return -1; /* error */
}
}
xfree (tokens);
return use;
}
/*
* Parse the usage parameter and set the keyflags. Returns -1 on
* error, 0 for no usage given or 1 for usage available.
*/
static int
parse_parameter_usage (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
int i;
if (!r)
return 0; /* none (this is an optional parameter)*/
i = parse_usagestr (r->u.value);
if (i == -1)
{
log_error ("%s:%d: invalid usage list\n", fname, r->lnr );
return -1; /* error */
}
r->u.usage = i;
return 1;
}
static int
parse_revocation_key (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
struct revocation_key revkey;
char *pn;
int i;
if( !r )
return 0; /* none (this is an optional parameter) */
pn = r->u.value;
revkey.class=0x80;
revkey.algid=atoi(pn);
if(!revkey.algid)
goto fail;
/* Skip to the fpr */
while(*pn && *pn!=':')
pn++;
if(*pn!=':')
goto fail;
pn++;
for(i=0;i<MAX_FINGERPRINT_LEN && *pn;i++,pn+=2)
{
int c=hextobyte(pn);
if(c==-1)
goto fail;
revkey.fpr[i]=c;
}
if (i != 20 && i != 32)
goto fail;
/* skip to the tag */
while(*pn && *pn!='s' && *pn!='S')
pn++;
if(ascii_strcasecmp(pn,"sensitive")==0)
revkey.class|=0x40;
memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key));
return 0;
fail:
log_error("%s:%d: invalid revocation key\n", fname, r->lnr );
return -1; /* error */
}
static u32
get_parameter_u32( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
if( !r )
return 0;
if (r->key == pKEYCREATIONDATE || r->key == pSUBKEYCREATIONDATE
|| r->key == pAUTHKEYCREATIONDATE)
return r->u.creation;
if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE )
return r->u.expire;
if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE )
return r->u.usage;
return (unsigned int)strtoul( r->u.value, NULL, 10 );
}
static unsigned int
get_parameter_uint( struct para_data_s *para, enum para_name key )
{
return get_parameter_u32( para, key );
}
static struct revocation_key *
get_parameter_revkey( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return r? &r->u.revkey : NULL;
}
static int
get_parameter_bool (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key);
return (r && r->u.abool);
}
static int
proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname,
struct output_control_s *outctrl, int card )
{
struct para_data_s *r;
const char *s1, *s2, *s3;
size_t n;
char *p;
int is_default = 0;
int have_user_id = 0;
int err, algo;
/* Check that we have all required parameters. */
r = get_parameter( para, pKEYTYPE );
if(r)
{
algo = get_parameter_algo (ctrl, para, pKEYTYPE, &is_default);
if (openpgp_pk_test_algo2 (algo, PUBKEY_USAGE_SIG))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
}
else
{
log_error ("%s: no Key-Type specified\n",fname);
return -1;
}
err = parse_parameter_usage (fname, para, pKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if key-usage is not provided and
no default algorithm has been requested. */
r = xmalloc_clear(sizeof(*r));
r->key = pKEYUSAGE;
r->u.usage = (is_default
? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG)
: openpgp_pk_algo_usage(algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n",
fname, r->lnr, algo);
return -1;
}
}
is_default = 0;
r = get_parameter( para, pSUBKEYTYPE );
if(r)
{
algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, &is_default);
if (openpgp_pk_test_algo (algo))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
err = parse_parameter_usage (fname, para, pSUBKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if subkey-usage is not
provided */
r = xmalloc_clear (sizeof(*r));
r->key = pSUBKEYUSAGE;
r->u.usage = (is_default
? PUBKEY_USAGE_ENC
: openpgp_pk_algo_usage (algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pSUBKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Subkey-Usage not allowed"
" for algo %d\n", fname, r->lnr, algo);
return -1;
}
}
}
if( get_parameter_value( para, pUSERID ) )
have_user_id=1;
else
{
/* create the formatted user ID */
s1 = get_parameter_value( para, pNAMEREAL );
s2 = get_parameter_value( para, pNAMECOMMENT );
s3 = get_parameter_value( para, pNAMEEMAIL );
if( s1 || s2 || s3 )
{
n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0);
r = xmalloc_clear( sizeof *r + n + 20 );
r->key = pUSERID;
p = r->u.value;
if( s1 )
p = stpcpy(p, s1 );
if( s2 )
p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")");
if( s3 )
{
/* If we have only the email part, do not add the space
* and the angle brackets. */
if (*r->u.value)
p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">");
else
p = stpcpy (p, s3);
}
append_to_parameter (para, r);
have_user_id=1;
}
}
if(!have_user_id)
{
log_error("%s: no User-ID specified\n",fname);
return -1;
}
/* Set preferences, if any. */
keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0);
/* Set keyserver, if any. */
s1=get_parameter_value( para, pKEYSERVER );
if(s1)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (s1, 1);
if(spec)
{
free_keyserver_spec(spec);
opt.def_keyserver_url=s1;
}
else
{
r = get_parameter (para, pKEYSERVER);
log_error("%s:%d: invalid keyserver url\n", fname, r->lnr );
return -1;
}
}
/* Set revoker, if any. */
if (parse_revocation_key (fname, para, pREVOKER))
return -1;
/* Make KEYCREATIONDATE from Creation-Date. We ignore this if the
* key has been taken from a card and a keycreationtime has already
* been set. This is so that we don't generate a key with a
* fingerprint different from the one stored on the OpenPGP card. */
r = get_parameter (para, pCREATIONDATE);
if (r && *r->u.value && !(get_parameter_bool (para, pCARDKEY)
&& get_parameter_u32 (para, pKEYCREATIONDATE)))
{
u32 seconds;
seconds = parse_creation_string (r->u.value);
if (!seconds)
{
log_error ("%s:%d: invalid creation date\n", fname, r->lnr );
return -1;
}
r->u.creation = seconds;
r->key = pKEYCREATIONDATE; /* Change that entry. */
}
/* Make KEYEXPIRE from Expire-Date. */
r = get_parameter( para, pEXPIREDATE );
if( r && *r->u.value )
{
u32 seconds;
seconds = parse_expire_string( r->u.value );
if( seconds == (u32)-1 )
{
log_error("%s:%d: invalid expire date\n", fname, r->lnr );
return -1;
}
r->u.expire = seconds;
r->key = pKEYEXPIRE; /* change hat entry */
/* also set it for the subkey */
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYEXPIRE;
r->u.expire = seconds;
append_to_parameter (para, r);
}
do_generate_keypair (ctrl, para, outctrl, card );
return 0;
}
/****************
* Kludge to allow non interactive key generation controlled
* by a parameter file.
* Note, that string parameters are expected to be in UTF-8
*/
static void
read_parameter_file (ctrl_t ctrl, const char *fname )
{
static struct { const char *name;
enum para_name key;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Curve", pKEYCURVE },
{ "Key-Usage", pKEYUSAGE },
{ "Subkey-Type", pSUBKEYTYPE },
{ "Subkey-Length", pSUBKEYLENGTH },
{ "Subkey-Curve", pSUBKEYCURVE },
{ "Subkey-Usage", pSUBKEYUSAGE },
{ "Name-Real", pNAMEREAL },
{ "Name-Email", pNAMEEMAIL },
{ "Name-Comment", pNAMECOMMENT },
{ "Expire-Date", pEXPIREDATE },
{ "Creation-Date", pCREATIONDATE },
{ "Passphrase", pPASSPHRASE },
{ "Preferences", pPREFERENCES },
{ "Revoker", pREVOKER },
{ "Handle", pHANDLE },
{ "Keyserver", pKEYSERVER },
{ "Keygrip", pKEYGRIP },
{ "Key-Grip", pKEYGRIP },
{ "Subkey-grip", pSUBKEYGRIP },
{ "Key-Version", pVERSION },
{ "Subkey-Version", pSUBVERSION },
{ NULL, 0 }
};
IOBUF fp;
byte *line;
unsigned int maxlen, nline;
char *p;
int lnr;
const char *err = NULL;
struct para_data_s *para, *r;
int i;
struct output_control_s outctrl;
memset( &outctrl, 0, sizeof( outctrl ) );
outctrl.pub.afx = new_armor_context ();
if( !fname || !*fname)
fname = "-";
fp = iobuf_open (fname);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp) {
log_error (_("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
lnr = 0;
err = NULL;
para = NULL;
maxlen = 1024;
line = NULL;
while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) {
char *keyword, *value;
lnr++;
if( !maxlen ) {
err = "line too long";
break;
}
for( p = line; isspace(*(byte*)p); p++ )
;
if( !*p || *p == '#' )
continue;
keyword = p;
if( *keyword == '%' ) {
for( ; !isspace(*(byte*)p); p++ )
;
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
value = p;
trim_trailing_ws( value, strlen(value) );
if( !ascii_strcasecmp( keyword, "%echo" ) )
log_info("%s\n", value );
else if( !ascii_strcasecmp( keyword, "%dry-run" ) )
outctrl.dryrun = 1;
else if( !ascii_strcasecmp( keyword, "%ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-protection" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION;
else if( !ascii_strcasecmp( keyword, "%transient-key" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY;
else if( !ascii_strcasecmp( keyword, "%commit" ) ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else if( !ascii_strcasecmp( keyword, "%pubring" ) ) {
if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) )
; /* still the same file - ignore it */
else {
xfree( outctrl.pub.newfname );
outctrl.pub.newfname = xstrdup( value );
outctrl.use_files = 1;
}
}
else if( !ascii_strcasecmp( keyword, "%secring" ) ) {
/* Ignore this command. */
}
else
log_info("skipping control '%s' (%s)\n", keyword, value );
continue;
}
if( !(p = strchr( p, ':' )) || p == keyword ) {
err = "missing colon";
break;
}
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
if( !*p ) {
err = "missing argument";
break;
}
value = p;
trim_trailing_ws( value, strlen(value) );
for(i=0; keywords[i].name; i++ ) {
if( !ascii_strcasecmp( keywords[i].name, keyword ) )
break;
}
if( !keywords[i].name ) {
err = "unknown keyword";
break;
}
if( keywords[i].key != pKEYTYPE && !para ) {
err = "parameter block does not start with \"Key-Type\"";
break;
}
if( keywords[i].key == pKEYTYPE && para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else {
for( r = para; r; r = r->next ) {
if( r->key == keywords[i].key )
break;
}
if( r ) {
err = "duplicate keyword";
break;
}
}
if (!opt.flags.rfc4880bis && (keywords[i].key == pVERSION
|| keywords[i].key == pSUBVERSION))
; /* Ignore version unless --rfc4880bis is active. */
else
{
r = xmalloc_clear( sizeof *r + strlen( value ) );
r->lnr = lnr;
r->key = keywords[i].key;
strcpy( r->u.value, value );
r->next = para;
para = r;
}
}
if( err )
log_error("%s:%d: %s\n", fname, lnr, err );
else if( iobuf_error (fp) ) {
log_error("%s:%d: read error\n", fname, lnr);
}
else if( para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created (get_parameter_value (para, pHANDLE));
}
if( outctrl.use_files ) { /* close open streams */
iobuf_close( outctrl.pub.stream );
/* Must invalidate that ugly cache to actually close it. */
if (outctrl.pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl.pub.fname);
xfree( outctrl.pub.fname );
xfree( outctrl.pub.newfname );
}
xfree (line);
release_parameter_list( para );
iobuf_close (fp);
release_armor_context (outctrl.pub.afx);
}
/* Helper for quick_generate_keypair. */
static struct para_data_s *
quickgen_set_para (struct para_data_s *para, int for_subkey,
int algo, int nbits, const char *curve, unsigned int use,
int version, const char *keygrip, u32 keytime)
{
struct para_data_s *r;
r = xmalloc_clear (sizeof *r + 30);
r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE;
if (use)
snprintf (r->u.value, 30, "%s%s%s%s",
(use & PUBKEY_USAGE_ENC)? "encr " : "",
(use & PUBKEY_USAGE_SIG)? "sign " : "",
(use & PUBKEY_USAGE_AUTH)? "auth " : "",
(use & PUBKEY_USAGE_CERT)? "cert " : "");
else
strcpy (r->u.value, for_subkey ? "encr" : "sign");
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE;
snprintf (r->u.value, 20, "%d", algo);
r->next = para;
para = r;
if (keygrip)
{
r = xmalloc_clear (sizeof *r + strlen (keygrip));
r->key = for_subkey? pSUBKEYGRIP : pKEYGRIP;
strcpy (r->u.value, keygrip);
r->next = para;
para = r;
}
else if (curve)
{
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH;
sprintf (r->u.value, "%u", nbits);
r->next = para;
para = r;
}
if (opt.flags.rfc4880bis)
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBVERSION : pVERSION;
snprintf (r->u.value, 20, "%d", version);
r->next = para;
para = r;
}
if (keytime)
{
r = xmalloc_clear (sizeof *r);
r->key = for_subkey? pSUBKEYCREATIONDATE : pKEYCREATIONDATE;
r->u.creation = keytime;
r->next = para;
para = r;
}
return para;
}
/*
* Unattended generation of a standard key.
*/
void
quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
int use_tty;
u32 keytime = 0;
memset (&outctrl, 0, sizeof outctrl);
use_tty = (!opt.batch && !opt.answer_yes
&& !*algostr && !*usagestr && !*expirestr
&& !cpr_enabled ()
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)));
r = xmalloc_clear (sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
uid = trim_spaces (r->u.value);
if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid)))
{
log_error (_("Key generation failed: %s\n"),
gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
/* If gpg is directly used on the console ask whether a key with the
given user id shall really be created. */
if (use_tty)
{
tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid);
if (!cpr_get_answer_is_yes_def ("quick_keygen.okay",
_("Continue? (Y/n) "), 1))
goto leave;
}
/* Check whether such a user ID already exists. */
{
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = uid;
kdbhd = keydb_new (ctrl);
if (!kdbhd)
goto leave;
err = keydb_search (kdbhd, &desc, 1, NULL);
keydb_release (kdbhd);
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_info (_("A key for \"%s\" already exists\n"), uid);
if (opt.answer_yes)
;
else if (!use_tty
|| !cpr_get_answer_is_yes_def ("quick_keygen.force",
_("Create anyway? (y/N) "), 0))
{
write_status_error ("genkey", gpg_error (304));
log_inc_errorcount (); /* we used log_info */
goto leave;
}
log_info (_("creating anyway\n"));
}
}
if (!*expirestr || strcmp (expirestr, "-") == 0)
expirestr = default_expiration_interval;
if ((!*algostr || !ascii_strcasecmp (algostr, "default")
|| !ascii_strcasecmp (algostr, "future-default")
|| !ascii_strcasecmp (algostr, "futuredefault")
|| !ascii_strcasecmp (algostr, "card"))
&& (!*usagestr || !ascii_strcasecmp (usagestr, "default")
|| !strcmp (usagestr, "-")))
{
/* Use default key parameters. */
int algo, subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
char *keygrip, *subkeygrip;
u32 subkeytime;
err = parse_key_parameter_string (ctrl, algostr, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&keygrip, &keytime,
&subalgo, &subsize, &subkeyuse,
&subcurve, &subversion,
&subkeygrip, &subkeytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version,
keygrip, keytime);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion, subkeygrip, subkeytime);
if (*expirestr)
{
u32 expire;
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
xfree (keygrip);
xfree (subkeygrip);
}
else
{
/* Extended unattended mode. Creates only the primary key. */
int algo, version;
unsigned int use;
u32 expire;
unsigned int nbits;
const char *curve;
char *keygrip;
err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version, &keygrip, &keytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
goto leave;
}
para = quickgen_set_para (para, 0, algo, nbits, curve, use, version,
keygrip, keytime);
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
xfree (keygrip);
}
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new key. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
r = xmalloc_clear (sizeof *r + strlen (s));
r->key = pPASSPHRASE;
strcpy (r->u.value, s);
r->next = para;
para = r;
}
/* If KEYTIME is set we know that the key has been taken from the
* card. Store that flag in the parameters. */
if (keytime)
{
r = xmalloc_clear (sizeof *r);
r->key = pCARDKEY;
r->u.abool = 1;
r->next = para;
para = r;
}
proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0);
leave:
release_parameter_list (para);
}
/*
* Generate a keypair (fname is only used in batch mode) If
* CARD_SERIALNO is not NULL the function will create the keys on an
* OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is
* NOT NULL, the encryption key for the card is generated on the host,
* imported to the card and a backup file created by gpg-agent. If
* FULL is not set only the basic prompts are used (except for batch
* mode).
*/
void
generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key)
{
gpg_error_t err;
unsigned int nbits;
char *uid = NULL;
int algo;
unsigned int use;
int both = 0;
u32 expire;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
#ifndef ENABLE_CARD_SUPPORT
(void)card_backup_key;
#endif
memset( &outctrl, 0, sizeof( outctrl ) );
if (opt.batch && card_serialno)
{
/* We don't yet support unattended key generation with a card
* serial number. */
log_error (_("can't do this in batch mode\n"));
print_further_info ("key generation with card serial number");
return;
}
if (opt.batch)
{
read_parameter_file (ctrl, fname);
return;
}
if (card_serialno)
{
#ifdef ENABLE_CARD_SUPPORT
struct agent_card_info_s info;
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"),
gpg_strerror (err));
return;
}
r = xcalloc (1, sizeof *r + strlen (card_serialno) );
r->key = pSERIALNO;
strcpy( r->u.value, card_serialno);
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[0].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy (r->u.value, "sign");
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[1].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy (r->u.value, "encrypt");
r->next = para;
para = r;
if (info.key_attr[1].algo == PUBKEY_ALGO_RSA)
{
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYLENGTH;
sprintf( r->u.value, "%u", info.key_attr[1].nbits);
r->next = para;
para = r;
}
else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_EDDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_ECDH)
{
r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, info.key_attr[1].curve);
r->next = para;
para = r;
}
r = xcalloc (1, sizeof *r + 20 );
r->key = pAUTHKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[2].algo );
r->next = para;
para = r;
if (card_backup_key)
{
r = xcalloc (1, sizeof *r + 1);
r->key = pCARDBACKUPKEY;
strcpy (r->u.value, "1");
r->next = para;
para = r;
}
#endif /*ENABLE_CARD_SUPPORT*/
}
else if (full) /* Full featured key generation. */
{
int subkey_algo;
char *key_from_hexgrip = NULL;
int cardkey;
u32 keytime;
algo = ask_algo (ctrl, 0, &subkey_algo, &use,
&key_from_hexgrip, &cardkey, &keytime);
if (key_from_hexgrip)
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 40 );
r->key = pKEYGRIP;
strcpy (r->u.value, key_from_hexgrip);
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r);
r->key = pCARDKEY;
r->u.abool = cardkey;
r->next = para;
para = r;
if (cardkey)
{
r = xmalloc_clear (sizeof *r);
r->key = pKEYCREATIONDATE;
r->u.creation = keytime;
r->next = para;
para = r;
}
xfree (key_from_hexgrip);
}
else
{
const char *curve = NULL;
if (subkey_algo)
{
/* Create primary and subkey at once. */
both = 1;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, &subkey_algo, NULL);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = 0;
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = ask_keysize (algo, 0);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy( r->u.value, "sign" );
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", subkey_algo);
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy( r->u.value, "encrypt" );
r->next = para;
para = r;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
if (algo == PUBKEY_ALGO_EDDSA
&& subkey_algo == PUBKEY_ALGO_ECDH)
{
/* Need to switch to a different curve for the
encryption key. */
curve = "Curve25519";
}
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
}
else /* Create only a single key. */
{
/* For ECC we need to ask for the curve before storing the
algo because ask_curve may change the algo. */
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, NULL, NULL);
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo );
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
nbits = 0;
}
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
/* The curve has already been set. */
}
else
{
nbits = ask_keysize (both? subkey_algo : algo, nbits);
r = xmalloc_clear( sizeof *r + 20 );
r->key = both? pSUBKEYLENGTH : pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
}
}
else /* Default key generation. */
{
int subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
char *keygrip, *subkeygrip;
u32 keytime, subkeytime;
tty_printf ( _("Note: Use \"%s %s\""
" for a full featured key generation dialog.\n"),
#if USE_GPG2_HACK
GPG_NAME "2"
#else
GPG_NAME
#endif
, "--full-generate-key" );
err = parse_key_parameter_string (ctrl, NULL, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&keygrip, &keytime,
&subalgo, &subsize,
&subkeyuse, &subcurve, &subversion,
&subkeygrip, &subkeytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
return;
}
para = quickgen_set_para (para, 0,
algo, size, curve, keyuse,
version, keygrip, keytime);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion, subkeygrip, subkeytime);
xfree (keygrip);
xfree (subkeygrip);
}
expire = full? ask_expire_interval (0, NULL)
: parse_expire_string (default_expiration_interval);
r = xcalloc (1, sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20);
r->key = pSUBKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
uid = ask_user_id (0, full, NULL);
if (!uid)
{
log_error(_("Key generation canceled.\n"));
release_parameter_list( para );
return;
}
r = xcalloc (1, sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno);
release_parameter_list (para);
}
/* Create and delete a dummy packet to start off a list of kbnodes. */
static void
start_tree(KBNODE *tree)
{
PACKET *pkt;
pkt=xmalloc_clear(sizeof(*pkt));
pkt->pkttype=PKT_NONE;
*tree=new_kbnode(pkt);
delete_kbnode(*tree);
}
/* Write the *protected* secret key to the file. */
static gpg_error_t
card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir)
{
gpg_error_t err = 0;
int rc;
char keyid_buffer[2 * 8 + 1];
char name_buffer[50];
char *fname;
IOBUF fp;
mode_t oldmask;
PACKET *pkt = NULL;
format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer));
snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer);
fname = make_filename (backup_dir, name_buffer, NULL);
/* Note that the umask call is not anymore needed because
iobuf_create now takes care of it. However, it does not harm
and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_create (fname, 1);
umask (oldmask);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) );
goto leave;
}
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SECRET_KEY;
pkt->pkt.secret_key = sk;
rc = build_packet (fp, pkt);
if (rc)
{
log_error ("build packet failed: %s\n", gpg_strerror (rc));
iobuf_cancel (fp);
}
else
{
char *fprbuf;
iobuf_close (fp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
log_info (_("Note: backup of card key saved to '%s'\n"), fname);
fprbuf = hexfingerprint (sk, NULL, 0);
if (!fprbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf,
fname, strlen (fname), 0);
xfree (fprbuf);
}
leave:
xfree (pkt);
xfree (fname);
return err;
}
/* Store key to card and make a backup file in OpenPGP format. */
static gpg_error_t
card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk,
const char *backup_dir)
{
PKT_public_key *sk;
gnupg_isotime_t timestamp;
gpg_error_t err;
char *hexgrip;
int rc;
struct agent_card_info_s info;
gcry_cipher_hd_t cipherhd = NULL;
char *cache_nonce = NULL;
void *kek = NULL;
size_t keklen;
sk = copy_public_key (NULL, sub_psk);
if (!sk)
return gpg_error_from_syserror ();
epoch2isotime (timestamp, (time_t)sk->timestamp);
err = hexkeygrip_from_pk (sk, &hexgrip);
if (err)
return err;
memset(&info, 0, sizeof (info));
rc = agent_scd_getattr ("SERIALNO", &info);
if (rc)
return (gpg_error_t)rc;
rc = agent_keytocard (hexgrip, 2, 1, info.serialno, timestamp);
xfree (info.serialno);
if (rc)
{
err = (gpg_error_t)rc;
goto leave;
}
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
err = receive_seckey_from_agent (ctrl, cipherhd, 0,
&cache_nonce, hexgrip, sk);
if (err)
{
log_error ("error getting secret key from agent: %s\n",
gpg_strerror (err));
goto leave;
}
err = card_write_key_to_backup_file (sk, backup_dir);
if (err)
log_error ("writing card key to backup file: %s\n", gpg_strerror (err));
else
/* Remove secret key data in agent side. */
agent_scd_learn (NULL, 1);
leave:
xfree (cache_nonce);
gcry_cipher_close (cipherhd);
xfree (kek);
xfree (hexgrip);
free_public_key (sk);
return err;
}
static void
do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card)
{
gpg_error_t err;
KBNODE pub_root = NULL;
const char *s;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
struct revocation_key *revkey;
int did_sub = 0;
u32 keytimestamp, subkeytimestamp, authkeytimestamp, signtimestamp;
char *cache_nonce = NULL;
int algo;
u32 expire;
const char *key_from_hexgrip = NULL;
int cardkey;
unsigned int keygen_flags;
if (outctrl->dryrun)
{
log_info("dry-run mode - key generation skipped\n");
return;
}
if ( outctrl->use_files )
{
if ( outctrl->pub.newfname )
{
iobuf_close(outctrl->pub.stream);
outctrl->pub.stream = NULL;
if (outctrl->pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl->pub.fname);
xfree( outctrl->pub.fname );
outctrl->pub.fname = outctrl->pub.newfname;
outctrl->pub.newfname = NULL;
if (is_secured_filename (outctrl->pub.fname) )
{
outctrl->pub.stream = NULL;
gpg_err_set_errno (EPERM);
}
else
outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0);
if (!outctrl->pub.stream)
{
log_error(_("can't create '%s': %s\n"), outctrl->pub.newfname,
strerror(errno) );
return;
}
if (opt.armor)
{
outctrl->pub.afx->what = 1;
push_armor_filter (outctrl->pub.afx, outctrl->pub.stream);
}
}
log_assert( outctrl->pub.stream );
if (opt.verbose)
log_info (_("writing public key to '%s'\n"), outctrl->pub.fname );
}
/* We create the packets as a tree of kbnodes. Because the
structure we create is known in advance we simply generate a
linked list. The first packet is a dummy packet which we flag as
deleted. The very first packet must always be a KEY packet. */
start_tree (&pub_root);
cardkey = get_parameter_bool (para, pCARDKEY);
/* In the case that the keys are created from the card we need to
* take the timestamps from the card. Only in this case a
* pSUBKEYCREATIONDATE or pAUTHKEYCREATIONDATE might be defined and
* then we need to use that so that the fingerprint of the subkey
* also matches the pre-computed and stored one on the card. In
* this case we also use the current time to create the
* self-signatures. */
keytimestamp = get_parameter_u32 (para, pKEYCREATIONDATE);
if (!keytimestamp)
keytimestamp = make_timestamp ();
subkeytimestamp = cardkey? get_parameter_u32 (para, pSUBKEYCREATIONDATE) : 0;
if (!subkeytimestamp)
subkeytimestamp = keytimestamp;
authkeytimestamp = cardkey? get_parameter_u32 (para, pAUTHKEYCREATIONDATE): 0;
if (!authkeytimestamp)
authkeytimestamp = keytimestamp;
signtimestamp = cardkey? make_timestamp () : keytimestamp;
/* log_debug ("XXX: cardkey ..: %d\n", cardkey); */
/* log_debug ("XXX: keytime ..: %s\n", isotimestamp (keytimestamp)); */
/* log_debug ("XXX: subkeytime: %s\n", isotimestamp (subkeytimestamp)); */
/* log_debug ("XXX: authkeytim: %s\n", isotimestamp (authkeytimestamp)); */
/* log_debug ("XXX: signtime .: %s\n", isotimestamp (signtimestamp)); */
/* Fixme: Check that this comment is still valid:
Note that, depending on the backend (i.e. the used scdaemon
version), the card key generation may update TIMESTAMP for each
key. Thus we need to pass TIMESTAMP to all signing function to
make sure that the binding signature is done using the timestamp
of the corresponding (sub)key and not that of the primary key.
An alternative implementation could tell the signing function the
node of the subkey but that is more work than just to pass the
current timestamp. */
algo = get_parameter_algo (ctrl, para, pKEYTYPE, NULL );
expire = get_parameter_u32( para, pKEYEXPIRE );
key_from_hexgrip = get_parameter_value (para, pKEYGRIP);
if (cardkey && !key_from_hexgrip)
BUG ();
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, cardkey,
pub_root,
keytimestamp,
expire, 0, keygen_flags);
else if (!card)
err = do_create (algo,
get_parameter_uint( para, pKEYLENGTH ),
get_parameter_value (para, pKEYCURVE),
pub_root,
keytimestamp,
expire, 0,
keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
else
err = gen_card_key (1, algo,
1, pub_root, &keytimestamp,
expire, keygen_flags);
/* Get the pointer to the generated public key packet. */
if (!err)
{
pri_psk = pub_root->next->pkt->pkt.public_key;
log_assert (pri_psk);
/* Make sure a few fields are correctly set up before going
further. */
pri_psk->flags.primary = 1;
keyid_from_pk (pri_psk, NULL);
/* We don't use pk_keyid to get keyid, because it also asserts
that main_keyid is set! */
keyid_copy (pri_psk->main_keyid, pri_psk->keyid);
}
if (!err && (revkey = get_parameter_revkey (para, pREVOKER)))
err = write_direct_sig (ctrl, pub_root, pri_psk,
revkey, signtimestamp, cache_nonce);
if (!err && (s = get_parameter_value (para, pUSERID)))
{
err = write_uid (pub_root, s );
if (!err)
err = write_selfsigs (ctrl, pub_root, pri_psk,
get_parameter_uint (para, pKEYUSAGE),
signtimestamp, cache_nonce);
}
/* Write the auth key to the card before the encryption key. This
is a partial workaround for a PGP bug (as of this writing, all
versions including 8.1), that causes it to try and encrypt to
the most recent subkey regardless of whether that subkey is
actually an encryption type. In this case, the auth key is an
RSA key so it succeeds. */
if (!err && card && get_parameter (para, pAUTHKEYTYPE))
{
err = gen_card_key (3, get_parameter_algo (ctrl, para,
pAUTHKEYTYPE, NULL ),
0, pub_root, &authkeytimestamp, expire, keygen_flags);
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, NULL,
PUBKEY_USAGE_AUTH, signtimestamp, cache_nonce);
}
if (!err && get_parameter (para, pSUBKEYTYPE))
{
int subkey_algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, NULL);
s = NULL;
key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP);
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pSUBVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, subkey_algo,
key_from_hexgrip, cardkey,
pub_root, subkeytimestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE),
1, keygen_flags);
else if (!card || (s = get_parameter_value (para, pCARDBACKUPKEY)))
{
err = do_create (subkey_algo,
get_parameter_uint (para, pSUBKEYLENGTH),
get_parameter_value (para, pSUBKEYCURVE),
pub_root,
subkeytimestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE), 1,
s? KEYGEN_FLAG_NO_PROTECTION : keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
kbnode_t node;
for (node = pub_root; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
log_assert (sub_psk);
if (s)
err = card_store_key_with_backup (ctrl,
sub_psk, gnupg_homedir ());
}
}
else
{
err = gen_card_key (2, subkey_algo, 0, pub_root,
&subkeytimestamp, expire, keygen_flags);
}
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, sub_psk,
get_parameter_uint (para, pSUBKEYUSAGE),
signtimestamp, cache_nonce);
did_sub = 1;
}
if (!err && outctrl->use_files) /* Direct write to specified files. */
{
err = write_keyblock (outctrl->pub.stream, pub_root);
if (err)
log_error ("can't write public key: %s\n", gpg_strerror (err));
}
else if (!err) /* Write to the standard keyrings. */
{
KEYDB_HANDLE pub_hd;
pub_hd = keydb_new (ctrl);
if (!pub_hd)
err = gpg_error_from_syserror ();
else
{
err = keydb_locate_writable (pub_hd);
if (err)
log_error (_("no writable public keyring found: %s\n"),
gpg_strerror (err));
}
if (!err && opt.verbose)
{
log_info (_("writing public key to '%s'\n"),
keydb_get_resource_name (pub_hd));
}
if (!err)
{
err = keydb_insert_keyblock (pub_hd, pub_root);
if (err)
log_error (_("error writing public keyring '%s': %s\n"),
keydb_get_resource_name (pub_hd), gpg_strerror (err));
}
keydb_release (pub_hd);
if (!err)
{
int no_enc_rsa;
PKT_public_key *pk;
no_enc_rsa = ((get_parameter_algo (ctrl, para, pKEYTYPE, NULL)
== PUBKEY_ALGO_RSA)
&& get_parameter_uint (para, pKEYUSAGE)
&& !((get_parameter_uint (para, pKEYUSAGE)
& PUBKEY_USAGE_ENC)) );
pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
keyid_from_pk (pk, pk->main_keyid);
register_trusted_keyid (pk->main_keyid);
update_ownertrust (ctrl, pk,
((get_ownertrust (ctrl, pk) & ~TRUST_MASK)
| TRUST_ULTIMATE ));
gen_standard_revoke (ctrl, pk, cache_nonce);
/* Get rid of the first empty packet. */
commit_kbnode (&pub_root);
if (!opt.batch)
{
tty_printf (_("public and secret key created and signed.\n") );
tty_printf ("\n");
merge_keys_and_selfsig (ctrl, pub_root);
list_keyblock_direct (ctrl, pub_root, 0, 1,
opt.fingerprint || opt.with_fingerprint,
1);
}
if (!opt.batch
&& (get_parameter_algo (ctrl, para,
pKEYTYPE, NULL) == PUBKEY_ALGO_DSA
|| no_enc_rsa )
&& !get_parameter (para, pSUBKEYTYPE) )
{
tty_printf(_("Note that this key cannot be used for "
"encryption. You may want to use\n"
"the command \"--edit-key\" to generate a "
"subkey for this purpose.\n") );
}
}
}
if (err)
{
if (opt.batch)
log_error ("key generation failed: %s\n", gpg_strerror (err) );
else
tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) );
write_status_error (card? "card_key_generate":"key_generate", err);
print_status_key_not_created ( get_parameter_value (para, pHANDLE) );
}
else
{
PKT_public_key *pk = find_kbnode (pub_root,
PKT_PUBLIC_KEY)->pkt->pkt.public_key;
print_status_key_created (did_sub? 'B':'P', pk,
get_parameter_value (para, pHANDLE));
}
release_kbnode (pub_root);
xfree (cache_nonce);
}
static gpg_error_t
parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage, u32 *r_expire,
unsigned int *r_nbits, const char **r_curve,
int *r_version, char **r_keygrip, u32 *r_keytime)
{
gpg_error_t err;
int algo;
unsigned int use, nbits;
u32 expire;
int wantuse;
int version = 4;
const char *curve = NULL;
*r_curve = NULL;
if (r_keygrip)
*r_keygrip = NULL;
if (r_keytime)
*r_keytime = 0;
nbits = 0;
/* Parse the algo string. */
if (algostr && *algostr == '&' && strlen (algostr) == 41)
{
/* Take algo from existing key. */
algo = check_keygrip (ctrl, algostr+1);
/* FIXME: We need the curve name as well. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
err = parse_key_parameter_string (ctrl, algostr, for_subkey? 1 : 0,
usagestr? parse_usagestr (usagestr):0,
&algo, &nbits, &use, &curve, &version,
r_keygrip, r_keytime,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return err;
}
/* Parse the usage string. */
if (!usagestr || !*usagestr
|| !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-"))
; /* Keep usage from parse_key_parameter_string. */
else if ((wantuse = parse_usagestr (usagestr)) != -1)
use = wantuse;
else
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_INV_VALUE);
}
/* Make sure a primary key has the CERT usage. */
if (!for_subkey)
use |= PUBKEY_USAGE_CERT;
/* Check that usage is possible. NB: We have the same check in
* parse_key_parameter_string but need it here again in case the
* separate usage value has been given. */
if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((use & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (use & PUBKEY_USAGE_CERT)))
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Parse the expire string. */
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_INV_VALUE);
}
if (curve)
*r_curve = curve;
*r_algo = algo;
*r_usage = use;
*r_expire = expire;
*r_nbits = nbits;
*r_version = version;
return 0;
}
/* Add a new subkey to an existing key. Returns 0 if a new key has
been generated and put into the keyblocks. If any of ALGOSTR,
USAGESTR, or EXPIRESTR is NULL interactive mode is used. */
gpg_error_t
generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err = 0;
int interactive;
kbnode_t node;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
int algo;
unsigned int use;
u32 expire;
unsigned int nbits = 0;
const char *curve = NULL;
u32 cur_time;
char *key_from_hexgrip = NULL;
u32 keytime = 0;
int cardkey = 0;
char *hexgrip = NULL;
char *serialno = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int keygen_flags = 0;
interactive = (!algostr || !usagestr || !expirestr);
/* Break out the primary key. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; primary key missing in keyblock!\n");
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
pri_psk = node->pkt->pkt.public_key;
cur_time = make_timestamp ();
if (pri_psk->timestamp > cur_time)
{
ulong d = pri_psk->timestamp - cur_time;
log_info ( d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_psk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
err = hexkeygrip_from_pk (pri_psk, &hexgrip);
if (err)
goto leave;
if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
{
if (interactive)
tty_printf (_("Secret parts of primary key are not available.\n"));
else
log_info ( _("Secret parts of primary key are not available.\n"));
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
if (serialno)
{
if (interactive)
tty_printf (_("Secret parts of primary key are stored on-card.\n"));
else
log_info ( _("Secret parts of primary key are stored on-card.\n"));
}
if (interactive)
{
algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip, &cardkey, NULL);
log_assert (algo);
if (key_from_hexgrip)
nbits = 0;
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
curve = ask_curve (&algo, NULL, NULL);
else
nbits = ask_keysize (algo, 0);
expire = ask_expire_interval (0, NULL);
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else /* Unattended mode. */
{
int version;
err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version, &key_from_hexgrip, &keytime);
if (err)
goto leave;
if (version == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
}
/* Verify the passphrase now so that we get a cache item for the
* primary key passphrase. The agent also returns a passphrase
* nonce, which we can use to set the passphrase for the subkey to
* that of the primary key. */
{
char *desc = gpg_format_keydesc (ctrl, pri_psk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
err = 0; /* Very likely that the key is on a card. */
if (err)
goto leave;
}
/* Start creation. */
if (key_from_hexgrip)
{
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, cardkey,
keyblock,
keytime? keytime : cur_time,
expire, 1,
keygen_flags);
}
else
{
const char *passwd;
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new subkey. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
passwd = get_static_passphrase ();
else
passwd = NULL;
err = do_create (algo, nbits, curve,
keyblock, cur_time, expire, 1, keygen_flags,
passwd, &cache_nonce, &passwd_nonce);
}
if (err)
goto leave;
/* Get the pointer to the generated public subkey packet. */
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
/* Write the binding signature. */
err = write_keybinding (ctrl, keyblock, pri_psk, sub_psk, use, cur_time,
cache_nonce);
if (err)
goto leave;
print_status_key_created ('S', sub_psk, NULL);
leave:
xfree (key_from_hexgrip);
xfree (hexgrip);
xfree (serialno);
xfree (cache_nonce);
xfree (passwd_nonce);
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
return err;
}
#ifdef ENABLE_CARD_SUPPORT
/* Generate a subkey on a card. */
gpg_error_t
generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock,
int keyno, const char *serialno)
{
gpg_error_t err = 0;
kbnode_t node;
PKT_public_key *pri_pk = NULL;
unsigned int use;
u32 expire;
u32 cur_time;
struct para_data_s *para = NULL;
PKT_public_key *sub_pk = NULL;
int algo;
struct agent_card_info_s info;
int keygen_flags = 0; /* FIXME!!! */
log_assert (keyno >= 1 && keyno <= 3);
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"), gpg_strerror (err));
return err;
}
algo = info.key_attr[keyno-1].algo;
para = xtrycalloc (1, sizeof *para + strlen (serialno) );
if (!para)
{
err = gpg_error_from_syserror ();
goto leave;
}
para->key = pSERIALNO;
strcpy (para->u.value, serialno);
/* Break out the primary secret key */
node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key lost!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pri_pk = node->pkt->pkt.public_key;
cur_time = make_timestamp();
if (pri_pk->timestamp > cur_time)
{
ulong d = pri_pk->timestamp - cur_time;
log_info (d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_pk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
expire = ask_expire_interval (0, NULL);
if (keyno == 1)
use = PUBKEY_USAGE_SIG;
else if (keyno == 2)
use = PUBKEY_USAGE_ENC;
else
use = PUBKEY_USAGE_AUTH;
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
/* Note, that depending on the backend, the card key generation may
update CUR_TIME. */
err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire,
keygen_flags);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
log_assert (sub_pk);
err = write_keybinding (ctrl, pub_keyblock, pri_pk, sub_pk,
use, cur_time, NULL);
}
leave:
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
else
print_status_key_created ('S', sub_pk, NULL);
release_parameter_list (para);
return err;
}
#endif /* !ENABLE_CARD_SUPPORT */
/*
* Write a keyblock to an output stream
*/
static int
write_keyblock( IOBUF out, KBNODE node )
{
for( ; node ; node = node->next )
{
if(!is_deleted_kbnode(node))
{
int rc = build_packet( out, node->pkt );
if( rc )
{
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
}
return 0;
}
/* Note that timestamp is an in/out arg.
* FIXME: Does not yet support v5 keys. */
static gpg_error_t
gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root,
u32 *timestamp, u32 expireval, int keygen_flags)
{
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t err;
PACKET *pkt;
PKT_public_key *pk;
char keyid[10];
unsigned char *public;
gcry_sexp_t s_key;
snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno);
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return gpg_error_from_syserror ();
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
xfree (pk);
return gpg_error_from_syserror ();
}
/* Note: SCD knows the serialnumber, thus there is no point in passing it. */
err = agent_scd_genkey (keyno, 1, timestamp);
/* The code below is not used because we force creation of
* the a card key (3rd arg).
* if (gpg_err_code (rc) == GPG_ERR_EEXIST)
* {
* tty_printf ("\n");
* log_error ("WARNING: key does already exists!\n");
* tty_printf ("\n");
* if ( cpr_get_answer_is_yes( "keygen.card.replace_key",
* _("Replace existing key? ")))
* rc = agent_scd_genkey (keyno, 1, timestamp);
* }
*/
if (err)
{
log_error ("key generation failed: %s\n", gpg_strerror (err));
xfree (pkt);
xfree (pk);
return err;
}
/* Send the READKEY command so that the agent creates a shadow key for
card key. We need to do that now so that we are able to create
the self-signatures. */
err = agent_readkey (NULL, 1, keyid, &public);
if (err)
return err;
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
return err;
if (algo == PUBKEY_ALGO_RSA)
err = key_from_sexp (pk->pkey, s_key, "public-key", "ne");
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, algo);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
gcry_sexp_release (s_key);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
free_public_key (pk);
return err;
}
pk->timestamp = *timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
#else
(void)keyno;
(void)is_primary;
(void)pub_root;
(void)timestamp;
(void)expireval;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!ENABLE_CARD_SUPPORT*/
}
diff --git a/g10/keyid.c b/g10/keyid.c
index 573958e39..0fdb18d88 100644
--- a/g10/keyid.c
+++ b/g10/keyid.c
@@ -1,1078 +1,1078 @@
/* keyid.c - key ID and fingerprint handling
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2004, 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "options.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "rmd160.h"
#include "../common/host2net.h"
#define KEYID_STR_SIZE 19
#ifdef HAVE_UNSIGNED_TIME_T
# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1))
#else
/* Error or 32 bit time_t and value after 2038-01-19. */
# define IS_INVALID_TIME_T(a) ((a) < 0)
#endif
/* Return a letter describing the public key algorithms. */
int
pubkey_letter( int algo )
{
switch (algo)
{
case PUBKEY_ALGO_RSA: return 'R' ;
case PUBKEY_ALGO_RSA_E: return 'r' ;
case PUBKEY_ALGO_RSA_S: return 's' ;
case PUBKEY_ALGO_ELGAMAL_E: return 'g' ;
case PUBKEY_ALGO_ELGAMAL: return 'G' ;
case PUBKEY_ALGO_DSA: return 'D' ;
case PUBKEY_ALGO_ECDH: return 'e' ; /* ECC DH (encrypt only) */
case PUBKEY_ALGO_ECDSA: return 'E' ; /* ECC DSA (sign only) */
case PUBKEY_ALGO_EDDSA: return 'E' ; /* ECC EdDSA (sign only) */
default: return '?';
}
}
/* Return a string describing the public key algorithm and the
keysize. For elliptic curves the function prints the name of the
curve because the keysize is a property of the curve. The string
is copied to the supplied buffer up a length of BUFSIZE-1.
Examples for the output are:
"rsa3072" - RSA with 3072 bit
"elg1024" - Elgamal with 1024 bit
"ed25519" - ECC using the curve Ed25519.
"E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
"E_1.3.6.1.4.1.11591.2.12242973" ECC with a bogus OID.
"unknown_N" - Unknown OpenPGP algorithm N.
If the option --legacy-list-mode is active, the output use the
legacy format:
"3072R" - RSA with 3072 bit
"1024g" - Elgamal with 1024 bit
"256E" - ECDSA using a curve with 256 bit
The macro PUBKEY_STRING_SIZE may be used to allocate a buffer with
a suitable size. Note that a more general version of this function
exists as get_keyalgo_string. However, that has no special
treatment for the old and unsupported Elgamal which we here print as
xxxNNNN. */
char *
pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize)
{
const char *prefix = NULL;
if (opt.legacy_list_mode)
{
snprintf (buffer, bufsize, "%4u%c",
nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo));
return buffer;
}
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: prefix = "rsa"; break;
case PUBKEY_ALGO_ELGAMAL_E: prefix = "elg"; break;
case PUBKEY_ALGO_DSA: prefix = "dsa"; break;
case PUBKEY_ALGO_ELGAMAL: prefix = "xxx"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA: prefix = ""; break;
}
if (prefix && *prefix)
snprintf (buffer, bufsize, "%s%u", prefix, nbits_from_pk (pk));
else if (prefix)
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
const char *name = openpgp_oid_to_curve (curve, 0);
if (name)
snprintf (buffer, bufsize, "%s", name);
else if (curve)
snprintf (buffer, bufsize, "E_%s", curve);
else
snprintf (buffer, bufsize, "E_error");
xfree (curve);
}
else
snprintf (buffer, bufsize, "unknown_%u", (unsigned int)pk->pubkey_algo);
return buffer;
}
/* Hash a public key. This function is useful for v4 and v5
* fingerprints and for v3 or v4 key signing. */
void
hash_public_key (gcry_md_hd_t md, PKT_public_key *pk)
{
unsigned int n;
unsigned int nn[PUBKEY_MAX_NPKEY];
byte *pp[PUBKEY_MAX_NPKEY];
int i;
unsigned int nbits;
size_t nbytes;
int npkey = pubkey_get_npkey (pk->pubkey_algo);
int is_v5 = pk->version == 5;
n = is_v5? 10 : 6;
/* FIXME: We can avoid the extra malloc by calling only the first
mpi_print here which computes the required length and calling the
real mpi_print only at the end. The speed advantage would only be
for ECC (opaque MPIs) or if we could implement an mpi_print
variant with a callback handler to do the hashing. */
if (npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
pp[0] = gcry_mpi_get_opaque (pk->pkey[0], &nbits);
nn[0] = (nbits+7)/8;
n+=nn[0];
}
else
{
for (i=0; i < npkey; i++ )
{
if (!pk->pkey[i])
{
/* This case may only happen if the parsing of the MPI
failed but the key was anyway created. May happen
during "gpg KEYFILE". */
pp[i] = NULL;
nn[i] = 0;
}
else if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const void *p;
p = gcry_mpi_get_opaque (pk->pkey[i], &nbits);
pp[i] = xmalloc ((nbits+7)/8);
if (p)
memcpy (pp[i], p, (nbits+7)/8);
else
pp[i] = NULL;
nn[i] = (nbits+7)/8;
n += nn[i];
}
else
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0,
&nbytes, pk->pkey[i]))
BUG ();
pp[i] = xmalloc (nbytes);
if (gcry_mpi_print (GCRYMPI_FMT_PGP, pp[i], nbytes,
&nbytes, pk->pkey[i]))
BUG ();
nn[i] = nbytes;
n += nn[i];
}
}
}
if (is_v5)
{
gcry_md_putc ( md, 0x9a ); /* ctb */
gcry_md_putc ( md, n >> 24 ); /* 4 byte length header */
gcry_md_putc ( md, n >> 16 );
gcry_md_putc ( md, n >> 8 );
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
}
else
{
gcry_md_putc ( md, 0x99 ); /* ctb */
gcry_md_putc ( md, n >> 8 ); /* 2 byte length header */
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
}
gcry_md_putc ( md, pk->timestamp >> 24 );
gcry_md_putc ( md, pk->timestamp >> 16 );
gcry_md_putc ( md, pk->timestamp >> 8 );
gcry_md_putc ( md, pk->timestamp );
gcry_md_putc ( md, pk->pubkey_algo );
if (is_v5)
{
n -= 10;
gcry_md_putc ( md, n >> 24 );
gcry_md_putc ( md, n >> 16 );
gcry_md_putc ( md, n >> 8 );
gcry_md_putc ( md, n );
}
if(npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
if (pp[0])
gcry_md_write (md, pp[0], nn[0]);
}
else
{
for(i=0; i < npkey; i++ )
{
if (pp[i])
gcry_md_write ( md, pp[i], nn[i] );
xfree(pp[i]);
}
}
}
/* fixme: Check whether we can replace this function or if not
describe why we need it. */
u32
v3_keyid (gcry_mpi_t a, u32 *ki)
{
byte *buffer, *p;
size_t nbytes;
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, a ))
BUG ();
/* fixme: allocate it on the stack */
buffer = xmalloc (nbytes);
if (gcry_mpi_print( GCRYMPI_FMT_USG, buffer, nbytes, NULL, a ))
BUG ();
if (nbytes < 8) /* oops */
ki[0] = ki[1] = 0;
else
{
p = buffer + nbytes - 8;
ki[0] = buf32_to_u32 (p);
p += 4;
ki[1] = buf32_to_u32 (p);
}
xfree (buffer);
return ki[1];
}
/* Return PK's keyid. The memory is owned by PK. */
u32 *
pk_keyid (PKT_public_key *pk)
{
keyid_from_pk (pk, NULL);
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) == 0
&& ! pk->flags.primary)
log_bug ("keyid and main_keyid are the same, but primary flag not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) != 0
&& pk->flags.primary)
log_bug ("keyid and main_keyid are different, but primary flag set!\n");
#endif
return pk->keyid;
}
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *
pk_main_keyid (PKT_public_key *pk)
{
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
#endif
return pk->main_keyid;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *
keyid_copy (u32 *dest, const u32 *src)
{
dest[0] = src[0];
dest[1] = src[1];
return dest;
}
char *
format_keyid (u32 *keyid, int format, char *buffer, int len)
{
char tmp[KEYID_STR_SIZE];
if (! buffer)
{
buffer = tmp;
len = sizeof (tmp);
}
if (format == KF_DEFAULT)
format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch (format)
{
case KF_NONE:
if (len)
*buffer = 0;
break;
case KF_SHORT:
snprintf (buffer, len, "%08lX", (ulong)keyid[1]);
break;
case KF_LONG:
snprintf (buffer, len, "%08lX%08lX", (ulong)keyid[0], (ulong)keyid[1]);
break;
case KF_0xSHORT:
snprintf (buffer, len, "0x%08lX", (ulong)keyid[1]);
break;
case KF_0xLONG:
snprintf (buffer, len, "0x%08lX%08lX", (ulong)keyid[0],(ulong)keyid[1]);
break;
default:
BUG();
}
if (buffer == tmp)
return xstrdup (buffer);
return buffer;
}
size_t
keystrlen(void)
{
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch(format)
{
case KF_NONE:
return 0;
case KF_SHORT:
return 8;
case KF_LONG:
return 16;
case KF_0xSHORT:
return 10;
case KF_0xLONG:
return 18;
default:
BUG();
}
}
const char *
keystr (u32 *keyid)
{
static char keyid_str[KEYID_STR_SIZE];
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
if (format == KF_NONE)
format = KF_LONG;
return format_keyid (keyid, format, keyid_str, sizeof (keyid_str));
}
/* This function returns the key id of the main and possible the
* subkey as one string. It is used by error messages. */
const char *
keystr_with_sub (u32 *main_kid, u32 *sub_kid)
{
static char buffer[KEYID_STR_SIZE+1+KEYID_STR_SIZE];
char *p;
int format = opt.keyid_format;
if (format == KF_NONE)
format = KF_LONG;
format_keyid (main_kid, format, buffer, KEYID_STR_SIZE);
if (sub_kid)
{
p = buffer + strlen (buffer);
*p++ = '/';
format_keyid (sub_kid, format, p, KEYID_STR_SIZE);
}
return buffer;
}
const char *
keystr_from_pk(PKT_public_key *pk)
{
keyid_from_pk(pk,NULL);
return keystr(pk->keyid);
}
const char *
keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk)
{
keyid_from_pk (main_pk, NULL);
if (sub_pk)
keyid_from_pk (sub_pk, NULL);
return keystr_with_sub (main_pk->keyid, sub_pk? sub_pk->keyid:NULL);
}
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *
pk_keyid_str (PKT_public_key *pk)
{
return keystr (pk_keyid (pk));
}
const char *
keystr_from_desc(KEYDB_SEARCH_DESC *desc)
{
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
case KEYDB_SEARCH_MODE_SHORT_KID:
return keystr(desc->u.kid);
case KEYDB_SEARCH_MODE_FPR:
{
u32 keyid[2];
if (desc->fprlen == 32)
{
keyid[0] = buf32_to_u32 (desc->u.fpr);
keyid[1] = buf32_to_u32 (desc->u.fpr+4);
}
else if (desc->fprlen == 20)
{
keyid[0] = buf32_to_u32 (desc->u.fpr+12);
keyid[1] = buf32_to_u32 (desc->u.fpr+16);
}
else if (desc->fprlen == 16)
return "?v3 fpr?";
else /* oops */
return "?vx fpr?";
return keystr(keyid);
}
default:
BUG();
}
}
/* Compute the fingerprint and keyid and store it in PK. */
static void
compute_fingerprint (PKT_public_key *pk)
{
const byte *dp;
gcry_md_hd_t md;
size_t len;
if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0))
BUG ();
hash_public_key (md, pk);
gcry_md_final (md);
dp = gcry_md_read (md, 0);
len = gcry_md_get_algo_dlen (gcry_md_get_algo (md));
log_assert (len <= MAX_FINGERPRINT_LEN);
memcpy (pk->fpr, dp, len);
pk->fprlen = len;
if (pk->version == 5)
{
pk->keyid[0] = buf32_to_u32 (dp);
pk->keyid[1] = buf32_to_u32 (dp+4);
}
else
{
pk->keyid[0] = buf32_to_u32 (dp+12);
pk->keyid[1] = buf32_to_u32 (dp+16);
}
gcry_md_close( md);
}
/*
* Get the keyid from the public key PK and store it at KEYID unless
* this is NULL. Returns the 32 bit short keyid.
*/
u32
keyid_from_pk (PKT_public_key *pk, u32 *keyid)
{
u32 dummy_keyid[2];
if (!keyid)
keyid = dummy_keyid;
if (!pk->fprlen)
compute_fingerprint (pk);
keyid[0] = pk->keyid[0];
keyid[1] = pk->keyid[1];
- return keyid[1]; /*FIXME:shortkeyid ist different for v5*/
+ return keyid[1]; /*FIXME:shortkeyid is different for v5*/
}
/*
* Get the keyid from the fingerprint. This function is simple for
* most keys, but has to do a key lookup for old v3 keys where the
* keyid is not part of the fingerprint.
*/
u32
keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint,
size_t fprint_len, u32 *keyid)
{
u32 dummy_keyid[2];
if( !keyid )
keyid = dummy_keyid;
if (fprint_len != 20 && fprint_len != 32)
{
/* This is special as we have to lookup the key first. */
PKT_public_key pk;
int rc;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_byfprint (ctrl, &pk, NULL, fprint, fprint_len);
if( rc )
{
log_printhex (fprint, fprint_len,
"Oops: keyid_from_fingerprint: no pubkey; fpr:");
keyid[0] = 0;
keyid[1] = 0;
}
else
keyid_from_pk (&pk, keyid);
}
else
{
const byte *dp = fprint;
if (fprint_len == 20) /* v4 key */
{
keyid[0] = buf32_to_u32 (dp+12);
keyid[1] = buf32_to_u32 (dp+16);
}
else /* v5 key */
{
keyid[0] = buf32_to_u32 (dp);
keyid[1] = buf32_to_u32 (dp+4);
}
}
return keyid[1];
}
u32
keyid_from_sig (PKT_signature *sig, u32 *keyid)
{
if( keyid )
{
keyid[0] = sig->keyid[0];
keyid[1] = sig->keyid[1];
}
return sig->keyid[1]; /*FIXME:shortkeyid*/
}
byte *
namehash_from_uid (PKT_user_id *uid)
{
if (!uid->namehash)
{
uid->namehash = xmalloc (20);
if (uid->attrib_data)
rmd160_hash_buffer (uid->namehash, uid->attrib_data, uid->attrib_len);
else
rmd160_hash_buffer (uid->namehash, uid->name, uid->len);
}
return uid->namehash;
}
/*
* Return the number of bits used in PK.
*/
unsigned int
nbits_from_pk (PKT_public_key *pk)
{
return pubkey_nbits (pk->pubkey_algo, pk->pkey);
}
/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return
* that string. The caller should pass a buffer with at least a size
* of MK_DATESTR_SIZE. */
char *
mk_datestr (char *buffer, size_t bufsize, u32 timestamp)
{
time_t atime = timestamp;
struct tm *tp;
if (IS_INVALID_TIME_T (atime))
strcpy (buffer, "????" "-??" "-??"); /* Mark this as invalid. */
else
{
tp = gmtime (&atime);
snprintf (buffer, bufsize, "%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/*
* return a string with the creation date of the pk
* Note: this is alloced in a static buffer.
* Format is: yyyy-mm-dd
*/
const char *
dateonlystr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, pk->timestamp);
}
/* Same as dateonlystr_from_pk but with a global option a full iso
* timestamp is returned. In this case it shares a static buffer with
* isotimestamp(). */
const char *
datestr_from_pk (PKT_public_key *pk)
{
if (opt.flags.full_timestrings)
return isotimestamp (pk->timestamp);
else
return dateonlystr_from_pk (pk);
}
const char *
dateonlystr_from_sig (PKT_signature *sig )
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, sig->timestamp);
}
const char *
datestr_from_sig (PKT_signature *sig )
{
if (opt.flags.full_timestrings)
return isotimestamp (sig->timestamp);
else
return dateonlystr_from_sig (sig);
}
const char *
expirestr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
if (!pk->expiredate)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (pk->expiredate);
return mk_datestr (buffer, sizeof buffer, pk->expiredate);
}
const char *
expirestr_from_sig (PKT_signature *sig)
{
static char buffer[MK_DATESTR_SIZE];
if (!sig->expiredate)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (sig->expiredate);
return mk_datestr (buffer, sizeof buffer, sig->expiredate);
}
const char *
revokestr_from_pk( PKT_public_key *pk )
{
static char buffer[MK_DATESTR_SIZE];
if(!pk->revoked.date)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (pk->revoked.date);
return mk_datestr (buffer, sizeof buffer, pk->revoked.date);
}
const char *
usagestr_from_pk (PKT_public_key *pk, int fill)
{
static char buffer[10];
int i = 0;
unsigned int use = pk->pubkey_usage;
if ( use & PUBKEY_USAGE_SIG )
buffer[i++] = 'S';
if ( use & PUBKEY_USAGE_CERT )
buffer[i++] = 'C';
if ( use & PUBKEY_USAGE_ENC )
buffer[i++] = 'E';
if ( (use & PUBKEY_USAGE_AUTH) )
buffer[i++] = 'A';
while (fill && i < 4)
buffer[i++] = ' ';
buffer[i] = 0;
return buffer;
}
const char *
colon_strtime (u32 t)
{
static char buf[20];
if (!t)
return "";
snprintf (buf, sizeof buf, "%lu", (ulong)t);
return buf;
}
const char *
colon_datestr_from_pk (PKT_public_key *pk)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)pk->timestamp);
return buf;
}
const char *
colon_datestr_from_sig (PKT_signature *sig)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)sig->timestamp);
return buf;
}
const char *
colon_expirestr_from_sig (PKT_signature *sig)
{
static char buf[20];
if (!sig->expiredate)
return "";
snprintf (buf, sizeof buf,"%lu", (ulong)sig->expiredate);
return buf;
}
/*
* Return a byte array with the fingerprint for the given PK/SK
* The length of the array is returned in ret_len. Caller must free
* the array or provide an array of length MAX_FINGERPRINT_LEN.
*/
byte *
fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len)
{
if (!pk->fprlen)
compute_fingerprint (pk);
if (!array)
array = xmalloc (pk->fprlen);
memcpy (array, pk->fpr, pk->fprlen);
if (ret_len)
*ret_len = pk->fprlen;
return array;
}
/* Return an allocated buffer with the fingerprint of PK formatted as
* a plain hexstring. If BUFFER is NULL the result is a malloc'd
* string. If BUFFER is not NULL the result will be copied into this
* buffer. In the latter case BUFLEN describes the length of the
* buffer; if this is too short the function terminates the process.
* Returns a malloc'ed string or BUFFER. A suitable length for BUFFER
* is (2*MAX_FINGERPRINT_LEN + 1). */
char *
hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen)
{
if (!pk->fprlen)
compute_fingerprint (pk);
if (!buffer)
{
buffer = xtrymalloc (2 * pk->fprlen + 1);
if (!buffer)
return NULL;
}
else if (buflen < 2 * pk->fprlen + 1)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
bin2hex (pk->fpr, pk->fprlen, buffer);
return buffer;
}
/* Pretty print a hex fingerprint. If BUFFER is NULL the result is a
malloc'd string. If BUFFER is not NULL the result will be copied
into this buffer. In the latter case BUFLEN describes the length
of the buffer; if this is too short the function terminates the
process. Returns a malloc'ed string or BUFFER. A suitable length
for BUFFER is (MAX_FORMATTED_FINGERPRINT_LEN + 1). */
char *
format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen)
{
int hexlen = strlen (fingerprint);
int space;
int i, j;
if (hexlen == 40) /* v4 fingerprint */
{
space = (/* The characters and the NUL. */
40 + 1
/* After every fourth character, we add a space (except
the last). */
+ 40 / 4 - 1
/* Half way through we add a second space. */
+ 1);
}
else if (hexlen == 64 || hexlen == 50) /* v5 fingerprint */
{
/* The v5 fingerprint is commonly printed truncated to 25
* octets. We accept the truncated as well as the full hex
* version here and format it like this:
* 19347 BC987 24640 25F99 DF3EC 2E000 0ED98 84892 E1F7B 3EA4C
*/
hexlen = 50;
space = 10 * 5 + 9 + 1;
}
else /* Other fingerprint versions - print as is. */
{
/* We truncated here so that we do not need to provide a buffer
* of a length which is in reality never used. */
if (hexlen > MAX_FORMATTED_FINGERPRINT_LEN - 1)
hexlen = MAX_FORMATTED_FINGERPRINT_LEN - 1;
space = hexlen + 1;
}
if (!buffer)
buffer = xmalloc (space);
else if (buflen < space)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
if (hexlen == 40) /* v4 fingerprint */
{
for (i = 0, j = 0; i < 40; i ++)
{
if (i && !(i % 4))
buffer[j ++] = ' ';
if (i == 40 / 2)
buffer[j ++] = ' ';
buffer[j ++] = fingerprint[i];
}
buffer[j ++] = 0;
log_assert (j == space);
}
else if (hexlen == 50) /* v5 fingerprint */
{
for (i=j=0; i < 50; i++)
{
if (i && !(i % 5))
buffer[j++] = ' ';
buffer[j++] = fingerprint[i];
}
buffer[j++] = 0;
log_assert (j == space);
}
else
{
mem2str (buffer, fingerprint, space);
}
return buffer;
}
/* Return the so called KEYGRIP which is the SHA-1 hash of the public
- key parameters expressed as an canoncial encoded S-Exp. ARRAY must
+ key parameters expressed as an canonical encoded S-Exp. ARRAY must
be 20 bytes long. Returns 0 on success or an error code. */
gpg_error_t
keygrip_from_pk (PKT_public_key *pk, unsigned char *array)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
if (DBG_PACKET)
log_debug ("get_keygrip for public key\n");
switch (pk->pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1],
pk->pkey[2], pk->pkey[3]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1], pk->pkey[2]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_S:
case GCRY_PK_RSA_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pk->pkey[0], pk->pkey[1]);
break;
case PUBKEY_ALGO_EDDSA:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
if (!curve)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_build (&s_pkey, NULL,
pk->pubkey_algo == PUBKEY_ALGO_EDDSA?
"(public-key(ecc(curve%s)(flags eddsa)(q%m)))":
(pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& openpgp_oid_is_cv25519 (pk->pkey[0]))?
"(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))":
"(public-key(ecc(curve%s)(q%m)))",
curve, pk->pkey[1]);
xfree (curve);
}
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (err)
return err;
if (!gcry_pk_get_keygrip (s_pkey, array))
{
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
log_info ("error computing keygrip (fpr=%s)\n", hexfpr);
xfree (hexfpr);
memset (array, 0, 20);
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
if (DBG_PACKET)
log_printhex (array, 20, "keygrip=");
/* FIXME: Save the keygrip in PK. */
}
gcry_sexp_release (s_pkey);
return err;
}
/* Store an allocated buffer with the keygrip of PK encoded as a
hexstring at r_GRIP. Returns 0 on success. */
gpg_error_t
hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip)
{
gpg_error_t err;
unsigned char grip[KEYGRIP_LEN];
*r_grip = NULL;
err = keygrip_from_pk (pk, grip);
if (!err)
{
char * buf = xtrymalloc (KEYGRIP_LEN * 2 + 1);
if (!buf)
err = gpg_error_from_syserror ();
else
{
bin2hex (grip, KEYGRIP_LEN, buf);
*r_grip = buf;
}
}
return err;
}
diff --git a/g10/keyring.c b/g10/keyring.c
index 5fa499759..992c280d2 100644
--- a/g10/keyring.c
+++ b/g10/keyring.c
@@ -1,1739 +1,1739 @@
/* keyring.c - keyring file handling
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1997-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "gpg.h"
#include "../common/util.h"
#include "keyring.h"
#include "packet.h"
#include "keydb.h"
#include "options.h"
#include "main.h" /*for check_key_signature()*/
#include "../common/i18n.h"
#include "../kbx/keybox.h"
typedef struct keyring_resource *KR_RESOURCE;
struct keyring_resource
{
struct keyring_resource *next;
int read_only;
dotlock_t lockhd;
int is_locked;
int did_full_scan;
char fname[1];
};
typedef struct keyring_resource const * CONST_KR_RESOURCE;
static KR_RESOURCE kr_resources;
struct keyring_handle
{
CONST_KR_RESOURCE resource;
struct {
CONST_KR_RESOURCE kr;
IOBUF iobuf;
int eof;
int error;
} current;
struct {
CONST_KR_RESOURCE kr;
off_t offset;
size_t pk_no;
size_t uid_no;
unsigned int n_packets; /*used for delete and update*/
} found, saved_found;
struct {
char *name;
char *pattern;
} word_match;
};
/* The number of extant handles. */
static int active_handles;
static int do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets );
/* We keep a cache of entries that we have entered in the DB. This
includes not only public keys, but also subkeys.
Note: we'd like to keep the offset of the items that are present,
however, this doesn't work, because another concurrent GnuPG
process could modify the keyring. */
struct key_present {
struct key_present *next;
u32 kid[2];
};
/* For the hash table, we use separate chaining with linked lists.
This means that we have an array of N linked lists (buckets), which
is indexed by KEYID[1] mod N. Elements present in the keyring will
be on the list; elements not present in the keyring will not be on
the list.
Note: since the hash table stores both present and not present
information, it cannot be used until we complete a full scan of the
keyring. This is indicated by key_present_hash_ready. */
typedef struct key_present **key_present_hash_t;
static key_present_hash_t key_present_hash;
static int key_present_hash_ready;
#define KEY_PRESENT_HASH_BUCKETS 2048
/* Allocate a new value for a key present hash table. */
static struct key_present *
key_present_value_new (void)
{
struct key_present *k;
k = xmalloc_clear (sizeof *k);
return k;
}
/* Allocate a new key present hash table. */
static key_present_hash_t
key_present_hash_new (void)
{
struct key_present **tbl;
tbl = xmalloc_clear (KEY_PRESENT_HASH_BUCKETS * sizeof *tbl);
return tbl;
}
/* Return whether the value described by KID if it is in the hash
table. Otherwise, return NULL. */
static struct key_present *
key_present_hash_lookup (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return k;
return NULL;
}
/* Add the key to the hash table TBL if it is not already present. */
static void
key_present_hash_update (key_present_hash_t tbl, u32 *kid)
{
struct key_present *k;
for (k = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))]; k; k = k->next)
{
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return;
}
k = key_present_value_new ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->next = tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))];
tbl[(kid[1] % (KEY_PRESENT_HASH_BUCKETS - 1))] = k;
}
/* Add all the keys (public and subkeys) present in the keyblock to
the hash TBL. */
static void
key_present_hash_update_from_kb (key_present_hash_t tbl, KBNODE node)
{
for (; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
keyid_from_pk (node->pkt->pkt.public_key, aki);
key_present_hash_update (tbl, aki);
}
}
}
/*
* Register a filename for plain keyring files. ptr is set to a
* pointer to be used to create a handles etc, or the already-issued
* pointer if it has already been registered. The function returns 1
* if a new keyring was registered.
*/
int
keyring_register_filename (const char *fname, int read_only, void **ptr)
{
KR_RESOURCE kr;
if (active_handles)
/* There are open handles. */
BUG ();
for (kr=kr_resources; kr; kr = kr->next)
{
if (same_file_p (kr->fname, fname))
{
/* Already registered. */
if (read_only)
kr->read_only = 1;
*ptr=kr;
return 0;
}
}
kr = xmalloc (sizeof *kr + strlen (fname));
strcpy (kr->fname, fname);
kr->read_only = read_only;
kr->lockhd = NULL;
kr->is_locked = 0;
kr->did_full_scan = 0;
/* keep a list of all issued pointers */
kr->next = kr_resources;
kr_resources = kr;
/* create the offset table the first time a function here is used */
if (!key_present_hash)
key_present_hash = key_present_hash_new ();
*ptr=kr;
return 1;
}
int
keyring_is_writable (void *token)
{
KR_RESOURCE r = token;
return r? (r->read_only || !access (r->fname, W_OK)) : 0;
}
/* Create a new handle for the resource associated with TOKEN.
On error NULL is returned and ERRNO is set.
The returned handle must be released using keyring_release (). */
KEYRING_HANDLE
keyring_new (void *token)
{
KEYRING_HANDLE hd;
KR_RESOURCE resource = token;
log_assert (resource);
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return hd;
hd->resource = resource;
active_handles++;
return hd;
}
void
keyring_release (KEYRING_HANDLE hd)
{
if (!hd)
return;
log_assert (active_handles > 0);
active_handles--;
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
iobuf_close (hd->current.iobuf);
xfree (hd);
}
/* Save the current found state in HD for later retrieval by
keybox_pop_found_state. Only one state may be saved. */
void
keyring_push_found_state (KEYRING_HANDLE hd)
{
hd->saved_found = hd->found;
hd->found.kr = NULL;
}
/* Restore the saved found state in HD. */
void
keyring_pop_found_state (KEYRING_HANDLE hd)
{
hd->found = hd->saved_found;
hd->saved_found.kr = NULL;
}
const char *
keyring_get_resource_name (KEYRING_HANDLE hd)
{
if (!hd || !hd->resource)
return NULL;
return hd->resource->fname;
}
/*
* Lock the keyring with the given handle, or unlock if YES is false.
* We ignore the handle and lock all registered files.
*/
int
keyring_lock (KEYRING_HANDLE hd, int yes)
{
KR_RESOURCE kr;
int rc = 0;
(void)hd;
if (yes) {
/* first make sure the lock handles are created */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->lockhd) {
kr->lockhd = dotlock_create (kr->fname, 0);
if (!kr->lockhd) {
log_info ("can't allocate lock for '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
}
}
if (rc)
return rc;
/* and now set the locks */
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (kr->is_locked)
continue;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to CloseHandle the file before we
* try to lock it. This is because another process might
* have taken the lock and is using keybox_file_rename to
* rename the base file. How if our dotlock_take below is
* waiting for the lock but we have the base file still
* open, keybox_file_rename will never succeed as we are
* in a deadlock. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0,
(char*)kr->fname);
#endif /*HAVE_W32_SYSTEM*/
if (dotlock_take (kr->lockhd, -1) ) {
log_info ("can't lock '%s'\n", kr->fname );
rc = GPG_ERR_GENERAL;
}
else
kr->is_locked = 1;
}
}
if (rc || !yes) {
for (kr=kr_resources; kr; kr = kr->next) {
if (!keyring_is_writable(kr))
continue;
if (!kr->is_locked)
continue;
if (dotlock_release (kr->lockhd))
log_info ("can't unlock '%s'\n", kr->fname );
else
kr->is_locked = 0;
}
}
return rc;
}
/*
* Return the last found keyblock. Caller must free it.
* The returned keyblock has the kbode flag bit 0 set for the node with
* the public key used to locate the keyblock or flag bit 1 set for
* the user ID node.
*/
int
keyring_get_keyblock (KEYRING_HANDLE hd, KBNODE *ret_kb)
{
PACKET *pkt;
struct parse_packet_ctx_s parsectx;
int rc;
KBNODE keyblock = NULL, node, lastnode;
IOBUF a;
int in_cert = 0;
int pk_no = 0;
int uid_no = 0;
int save_mode;
if (ret_kb)
*ret_kb = NULL;
if (!hd->found.kr)
return -1; /* no successful search */
a = iobuf_open (hd->found.kr->fname);
if (!a)
{
log_error(_("can't open '%s'\n"), hd->found.kr->fname);
return GPG_ERR_KEYRING_OPEN;
}
if (iobuf_seek (a, hd->found.offset) ) {
log_error ("can't seek '%s'\n", hd->found.kr->fname);
iobuf_close(a);
return GPG_ERR_KEYRING_OPEN;
}
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
hd->found.n_packets = 0;
lastnode = NULL;
save_mode = set_packet_list_mode(0);
while ((rc=parse_packet (&parsectx, pkt)) != -1) {
hd->found.n_packets = parsectx.n_parsed_packets;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET) {
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
if (in_cert)
/* It is not this key that is problematic, but the
following key. */
{
rc = 0;
hd->found.n_packets --;
}
else
/* Upper layer needs to handle this. */
{
}
break;
}
if (rc) {
log_error ("keyring_get_keyblock: read error: %s\n",
gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
break;
}
/* Filter allowed packets. */
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_SIGNATURE:
break; /* Allowed per RFC. */
case PKT_RING_TRUST:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
break; /* Allowed by us. */
default:
log_info ("skipped packet of type %d in keyring\n",
(int)pkt->pkttype);
free_packet(pkt, &parsectx);
init_packet(pkt);
continue;
}
if (in_cert && (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)) {
hd->found.n_packets--; /* fix counter */
break; /* ready */
}
in_cert = 1;
node = new_kbnode (pkt);
if (!keyblock)
keyblock = lastnode = node;
else
{
lastnode->next = node;
lastnode = node;
}
switch (pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
if (++pk_no == hd->found.pk_no)
node->flag |= 1;
break;
case PKT_USER_ID:
if (++uid_no == hd->found.uid_no)
node->flag |= 2;
break;
default:
break;
}
pkt = xmalloc (sizeof *pkt);
init_packet(pkt);
}
set_packet_list_mode(save_mode);
if (rc == -1 && keyblock)
rc = 0; /* got the entire keyblock */
if (rc || !ret_kb)
release_kbnode (keyblock);
else {
*ret_kb = keyblock;
}
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree (pkt);
iobuf_close(a);
/* Make sure that future search operations fail immediately when
* we know that we are working on a invalid keyring
*/
if (gpg_err_code (rc) == GPG_ERR_INV_KEYRING)
hd->current.error = rc;
return rc;
}
int
keyring_update_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* The open iobuf isn't needed anymore and in fact is a problem when
it comes to renaming the keyring files on some operating systems,
so close it here */
iobuf_close(hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the update */
rc = do_copy (3, hd->found.kr->fname, kb,
hd->found.offset, hd->found.n_packets );
if (!rc) {
if (key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
}
return rc;
}
int
keyring_insert_keyblock (KEYRING_HANDLE hd, KBNODE kb)
{
int rc;
const char *fname;
if (!hd)
fname = NULL;
else if (hd->found.kr)
{
fname = hd->found.kr->fname;
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else if (hd->current.kr)
{
fname = hd->current.kr->fname;
if (hd->current.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
}
else
fname = hd->resource? hd->resource->fname:NULL;
if (!fname)
return GPG_ERR_GENERAL;
/* Close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
- * after the write opertions.
+ * after the write operations.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the insert */
rc = do_copy (1, fname, kb, 0, 0 );
if (!rc && key_present_hash)
{
key_present_hash_update_from_kb (key_present_hash, kb);
}
return rc;
}
int
keyring_delete_keyblock (KEYRING_HANDLE hd)
{
int rc;
if (!hd->found.kr)
return -1; /* no successful prior search */
if (hd->found.kr->read_only)
return gpg_error (GPG_ERR_EACCES);
if (!hd->found.n_packets) {
/* need to know the number of packets - do a dummy get_keyblock*/
rc = keyring_get_keyblock (hd, NULL);
if (rc) {
log_error ("re-reading keyblock failed: %s\n", gpg_strerror (rc));
return rc;
}
if (!hd->found.n_packets)
BUG ();
}
/* close this one otherwise we will lose the position for
* a next search. Fixme: it would be better to adjust the position
- * after the write opertions.
+ * after the write operations.
*/
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
/* do the delete */
rc = do_copy (2, hd->found.kr->fname, NULL,
hd->found.offset, hd->found.n_packets );
if (!rc) {
/* better reset the found info */
hd->found.kr = NULL;
hd->found.offset = 0;
/* Delete is a rare operations, so we don't remove the keys
* from the offset table */
}
return rc;
}
/*
* Start the next search on this handle right at the beginning
*/
int
keyring_search_reset (KEYRING_HANDLE hd)
{
log_assert (hd);
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.eof = 0;
hd->current.error = 0;
hd->found.kr = NULL;
hd->found.offset = 0;
if (hd->current.kr)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0,
(char*)hd->current.kr->fname);
hd->current.kr = NULL;
return 0;
}
static int
prepare_search (KEYRING_HANDLE hd)
{
if (hd->current.error) {
/* If the last key was a legacy key, we simply ignore the error so that
we can easily use search_next. */
if (gpg_err_code (hd->current.error) == GPG_ERR_LEGACY_KEY)
{
if (DBG_LOOKUP)
log_debug ("%s: last error was GPG_ERR_LEGACY_KEY, clearing\n",
__func__);
hd->current.error = 0;
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: returning last error: %s\n",
__func__, gpg_strerror (hd->current.error));
return hd->current.error; /* still in error state */
}
}
if (hd->current.kr && !hd->current.eof) {
if ( !hd->current.iobuf )
{
if (DBG_LOOKUP)
log_debug ("%s: missing iobuf!\n", __func__);
return GPG_ERR_GENERAL; /* Position invalid after a modify. */
}
return 0; /* okay */
}
if (!hd->current.kr && hd->current.eof)
{
if (DBG_LOOKUP)
log_debug ("%s: EOF!\n", __func__);
return -1; /* still EOF */
}
if (!hd->current.kr) { /* start search with first keyring */
hd->current.kr = hd->resource;
if (!hd->current.kr) {
if (DBG_LOOKUP)
log_debug ("%s: keyring not available!\n", __func__);
hd->current.eof = 1;
return -1; /* keyring not available */
}
log_assert (!hd->current.iobuf);
}
else { /* EOF */
if (DBG_LOOKUP)
log_debug ("%s: EOF\n", __func__);
iobuf_close (hd->current.iobuf);
hd->current.iobuf = NULL;
hd->current.kr = NULL;
hd->current.eof = 1;
return -1;
}
hd->current.eof = 0;
hd->current.iobuf = iobuf_open (hd->current.kr->fname);
if (!hd->current.iobuf)
{
hd->current.error = gpg_error_from_syserror ();
log_error(_("can't open '%s'\n"), hd->current.kr->fname );
return hd->current.error;
}
return 0;
}
/* A map of the all characters valid used for word_match()
* Valid characters are in this table converted to uppercase.
* because the upper 128 bytes have special meaning, we assume
* that they are all valid.
* Note: We must use numerical values here in case that this program
* will be converted to those little blue HAL9000s with their strange
* EBCDIC character set (user ids are UTF-8).
* wk 2000-04-13: Hmmm, does this really make sense, given the fact that
* we can run gpg now on a S/390 running GNU/Linux, where the code
* translation is done by the device drivers?
*/
static const byte word_match_chars[256] = {
/* 00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 08 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 10 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 18 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 20 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/* 38 */ 0x38, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 40 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 58 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 60 */ 0x00, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 68 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
/* 70 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
/* 78 */ 0x58, 0x59, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
/* 88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
/* 90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
/* 98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
/* a0 */ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
/* a8 */ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
/* b0 */ 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
/* b8 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
/* c0 */ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
/* c8 */ 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
/* d0 */ 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
/* d8 */ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
/* e0 */ 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
/* e8 */ 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
/* f0 */ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
/* f8 */ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
/****************
* Do a word match (original user id starts with a '+').
* The pattern is already tokenized to a more suitable format:
* There are only the real words in it delimited by one space
* and all converted to uppercase.
*
* Returns: 0 if all words match.
*
* Note: This algorithm is a straightforward one and not very
* fast. It works for UTF-8 strings. The uidlen should
* be removed but due to the fact that old versions of
* pgp don't use UTF-8 we still use the length; this should
* be fixed in parse-packet (and replace \0 by some special
* UTF-8 encoding)
*/
static int
word_match( const byte *uid, size_t uidlen, const byte *pattern )
{
size_t wlen, n;
const byte *p;
const byte *s;
for( s=pattern; *s; ) {
do {
/* skip leading delimiters */
while( uidlen && !word_match_chars[*uid] )
uid++, uidlen--;
/* get length of the word */
n = uidlen; p = uid;
while( n && word_match_chars[*p] )
p++, n--;
wlen = p - uid;
/* and compare against the current word from pattern */
for(n=0, p=uid; n < wlen && s[n] != ' ' && s[n] ; n++, p++ ) {
if( word_match_chars[*p] != s[n] )
break;
}
if( n == wlen && (s[n] == ' ' || !s[n]) )
break; /* found */
uid += wlen;
uidlen -= wlen;
} while( uidlen );
if( !uidlen )
return -1; /* not found */
/* advance to next word in pattern */
for(; *s != ' ' && *s ; s++ )
;
if( *s )
s++ ;
}
return 0; /* found */
}
/****************
* prepare word word_match; that is parse the name and
* build the pattern.
* caller has to free the returned pattern
*/
static char*
prepare_word_match (const byte *name)
{
byte *pattern, *p;
int c;
/* the original length is always enough for the pattern */
p = pattern = xmalloc(strlen(name)+1);
do {
/* skip leading delimiters */
while( *name && !word_match_chars[*name] )
name++;
/* copy as long as we don't have a delimiter and convert
* to uppercase.
* fixme: how can we handle utf8 uppercasing */
for( ; *name && (c=word_match_chars[*name]); name++ )
*p++ = c;
*p++ = ' '; /* append pattern delimiter */
} while( *name );
p[-1] = 0; /* replace last pattern delimiter by EOS */
return pattern;
}
static int
compare_name (int mode, const char *name, const char *uid, size_t uidlen)
{
int i;
const char *s, *se;
if (mode == KEYDB_SEARCH_MODE_EXACT) {
for (i=0; name[i] && uidlen; i++, uidlen--)
if (uid[i] != name[i])
break;
if (!uidlen && !name[i])
return 0; /* found */
}
else if (mode == KEYDB_SEARCH_MODE_SUBSTR) {
if (ascii_memistr( uid, uidlen, name ))
return 0;
}
else if ( mode == KEYDB_SEARCH_MODE_MAIL
|| mode == KEYDB_SEARCH_MODE_MAILSUB
|| mode == KEYDB_SEARCH_MODE_MAILEND) {
int have_angles = 1;
for (i=0, s= uid; i < uidlen && *s != '<'; s++, i++)
;
if (i == uidlen)
{
/* The UID is a plain addr-spec (cf. RFC2822 section 4.3). */
have_angles = 0;
s = uid;
i = 0;
}
if (i < uidlen) {
if (have_angles)
{
/* skip opening delim and one char and look for the closing one*/
s++; i++;
for (se=s+1, i++; i < uidlen && *se != '>'; se++, i++)
;
}
else
se = s + uidlen;
if (i < uidlen) {
i = se - s;
if (mode == KEYDB_SEARCH_MODE_MAIL) {
if( strlen(name)-2 == i
&& !ascii_memcasecmp( s, name+1, i) )
return 0;
}
else if (mode == KEYDB_SEARCH_MODE_MAILSUB) {
if( ascii_memistr( s, i, name ) )
return 0;
}
else { /* email from end */
/* nyi */
}
}
}
}
else if (mode == KEYDB_SEARCH_MODE_WORDS)
return word_match (uid, uidlen, name);
else
BUG();
return -1; /* not found */
}
/*
* Search through the keyring(s), starting at the current position,
* for a keyblock which contains one of the keys described in the DESC array.
*/
int
keyring_search (KEYRING_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex, int ignore_legacy)
{
int rc;
PACKET pkt;
struct parse_packet_ctx_s parsectx;
int save_mode;
off_t offset, main_offset;
size_t n;
int need_uid, need_words, need_keyid, need_fpr, any_skip;
int pk_no, uid_no;
int initial_skip;
int scanned_from_start;
int use_key_present_hash;
PKT_user_id *uid = NULL;
PKT_public_key *pk = NULL;
u32 aki[2];
/* figure out what information we need */
need_uid = need_words = need_keyid = need_fpr = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
need_uid = 1;
break;
case KEYDB_SEARCH_MODE_WORDS:
need_uid = 1;
need_words = 1;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
need_keyid = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
need_fpr = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keyring_search_reset (hd);
break;
default: break;
}
if (desc[n].skipfnc)
{
any_skip = 1;
need_keyid = 1;
}
}
if (DBG_LOOKUP)
log_debug ("%s: need_uid = %d; need_words = %d; need_keyid = %d; need_fpr = %d; any_skip = %d\n",
__func__, need_uid, need_words, need_keyid, need_fpr, any_skip);
rc = prepare_search (hd);
if (rc)
{
if (DBG_LOOKUP)
log_debug ("%s: prepare_search failed: %s (%d)\n",
__func__, gpg_strerror (rc), gpg_err_code (rc));
return rc;
}
use_key_present_hash = !!key_present_hash;
if (!use_key_present_hash)
{
if (DBG_LOOKUP)
log_debug ("%s: no offset table.\n", __func__);
}
else if (!key_present_hash_ready)
{
if (DBG_LOOKUP)
log_debug ("%s: initializing offset table. (need_keyid: %d => 1)\n",
__func__, need_keyid);
need_keyid = 1;
}
else if (ndesc == 1 && desc[0].mode == KEYDB_SEARCH_MODE_LONG_KID)
{
struct key_present *oi;
if (DBG_LOOKUP)
log_debug ("%s: look up by long key id, checking cache\n", __func__);
oi = key_present_hash_lookup (key_present_hash, desc[0].u.kid);
if (!oi)
{ /* We know that we don't have this key */
if (DBG_LOOKUP)
log_debug ("%s: cache says not present\n", __func__);
hd->found.kr = NULL;
hd->current.eof = 1;
return -1;
}
/* We could now create a positive search status and return.
* However the problem is that another instance of gpg may
* have changed the keyring so that the offsets are not valid
* anymore - therefore we don't do it
*/
}
if (need_words)
{
const char *name = NULL;
log_debug ("word search mode does not yet work\n");
/* FIXME: here is a long standing bug in our function and in addition we
just use the first search description */
for (n=0; n < ndesc && !name; n++)
{
if (desc[n].mode == KEYDB_SEARCH_MODE_WORDS)
name = desc[n].u.name;
}
log_assert (name);
if ( !hd->word_match.name || strcmp (hd->word_match.name, name) )
{
/* name changed */
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
hd->word_match.name = xstrdup (name);
hd->word_match.pattern = prepare_word_match (name);
}
/* name = hd->word_match.pattern; */
}
init_packet(&pkt);
save_mode = set_packet_list_mode(0);
hd->found.kr = NULL;
main_offset = 0;
pk_no = uid_no = 0;
initial_skip = 1; /* skip until we see the start of a keyblock */
scanned_from_start = iobuf_tell (hd->current.iobuf) == 0;
if (DBG_LOOKUP)
log_debug ("%s: %ssearching from start of resource.\n",
__func__, scanned_from_start ? "" : "not ");
init_parse_packet (&parsectx, hd->current.iobuf);
while (1)
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
rc = search_packet (&parsectx, &pkt, &offset, need_uid);
if (ignore_legacy && gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
{
free_packet (&pkt, &parsectx);
continue;
}
if (rc)
break;
if (pkt.pkttype == PKT_PUBLIC_KEY || pkt.pkttype == PKT_SECRET_KEY)
{
main_offset = offset;
pk_no = uid_no = 0;
initial_skip = 0;
}
if (initial_skip)
{
free_packet (&pkt, &parsectx);
continue;
}
pk = NULL;
uid = NULL;
if ( pkt.pkttype == PKT_PUBLIC_KEY
|| pkt.pkttype == PKT_PUBLIC_SUBKEY
|| pkt.pkttype == PKT_SECRET_KEY
|| pkt.pkttype == PKT_SECRET_SUBKEY)
{
pk = pkt.pkt.public_key;
++pk_no;
if (need_fpr)
{
fingerprint_from_pk (pk, afp, &an);
while (an < 32) /* fill up to 32 bytes */
afp[an++] = 0;
}
if (need_keyid)
keyid_from_pk (pk, aki);
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
key_present_hash_update (key_present_hash, aki);
}
else if (pkt.pkttype == PKT_USER_ID)
{
uid = pkt.pkt.user_id;
++uid_no;
}
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode) {
case KEYDB_SEARCH_MODE_NONE:
BUG ();
break;
case KEYDB_SEARCH_MODE_EXACT:
case KEYDB_SEARCH_MODE_SUBSTR:
case KEYDB_SEARCH_MODE_MAIL:
case KEYDB_SEARCH_MODE_MAILSUB:
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
if ( uid && !compare_name (desc[n].mode,
desc[n].u.name,
uid->name, uid->len))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
if (pk && desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (pk && desc[n].u.kid[0] == aki[0]
&& desc[n].u.kid[1] == aki[1])
goto found;
break;
case KEYDB_SEARCH_MODE_FPR:
if (pk && desc[n].fprlen >= 16 && desc[n].fprlen <= 32
&& !memcmp (desc[n].u.fpr, afp, desc[n].fprlen))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
if (pk)
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
if (pk)
goto found;
break;
default:
rc = GPG_ERR_INV_ARG;
goto found;
}
}
free_packet (&pkt, &parsectx);
continue;
found:
if (rc)
goto real_found;
if (DBG_LOOKUP)
log_debug ("%s: packet starting at offset %lld matched descriptor %zu\n"
, __func__, (long long)offset, n);
/* Record which desc we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(descindex)
*descindex=n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
if (desc[n].skipfnc
&& desc[n].skipfnc (desc[n].skipfncvalue, aki, uid_no))
{
if (DBG_LOOKUP)
log_debug ("%s: skipping match: desc %zd's skip function returned TRUE\n",
__func__, n);
break;
}
}
if (n == ndesc)
goto real_found;
free_packet (&pkt, &parsectx);
}
real_found:
if (!rc)
{
if (DBG_LOOKUP)
log_debug ("%s: returning success\n", __func__);
hd->found.offset = main_offset;
hd->found.kr = hd->current.kr;
hd->found.pk_no = pk? pk_no : 0;
hd->found.uid_no = uid? uid_no : 0;
}
else if (rc == -1)
{
if (DBG_LOOKUP)
log_debug ("%s: no matches (EOF)\n", __func__);
hd->current.eof = 1;
/* if we scanned all keyrings, we are sure that
* all known key IDs are in our offtbl, mark that. */
if (use_key_present_hash
&& !key_present_hash_ready
&& scanned_from_start)
{
KR_RESOURCE kr;
/* First set the did_full_scan flag for this keyring. */
for (kr=kr_resources; kr; kr = kr->next)
{
if (hd->resource == kr)
{
kr->did_full_scan = 1;
break;
}
}
/* Then check whether all flags are set and if so, mark the
offtbl ready */
for (kr=kr_resources; kr; kr = kr->next)
{
if (!kr->did_full_scan)
break;
}
if (!kr)
key_present_hash_ready = 1;
}
}
else
{
if (DBG_LOOKUP)
log_debug ("%s: error encountered during search: %s (%d)\n",
__func__, gpg_strerror (rc), rc);
hd->current.error = rc;
}
free_packet (&pkt, &parsectx);
deinit_parse_packet (&parsectx);
set_packet_list_mode(save_mode);
return rc;
}
static int
create_tmp_file (const char *template,
char **r_bakfname, char **r_tmpfname, IOBUF *r_fp)
{
gpg_error_t err;
mode_t oldmask;
err = keybox_tmp_names (template, 1, r_bakfname, r_tmpfname);
if (err)
return err;
/* Create the temp file with limited access. Note that the umask
call is not anymore needed because iobuf_create now takes care of
it. However, it does not harm and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (*r_tmpfname))
{
*r_fp = NULL;
gpg_err_set_errno (EPERM);
}
else
*r_fp = iobuf_create (*r_tmpfname, 1);
umask (oldmask);
if (!*r_fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), *r_tmpfname, gpg_strerror (err));
xfree (*r_tmpfname);
*r_tmpfname = NULL;
xfree (*r_bakfname);
*r_bakfname = NULL;
}
return err;
}
static int
rename_tmp_file (const char *bakfname, const char *tmpfname, const char *fname)
{
int rc = 0;
int block = 0;
/* Invalidate close caches. */
if (iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)tmpfname ))
{
rc = gpg_error_from_syserror ();
goto fail;
}
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)bakfname );
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname );
/* First make a backup file. */
block = 1;
rc = gnupg_rename_file (fname, bakfname, &block);
if (rc)
goto fail;
/* then rename the file */
rc = gnupg_rename_file (tmpfname, fname, NULL);
if (block)
{
gnupg_unblock_all_signals ();
block = 0;
}
if (rc)
{
register_secured_file (fname);
goto fail;
}
/* Now make sure the file has the same permissions as the original */
#ifndef HAVE_DOSISH_SYSTEM
{
struct stat statbuf;
statbuf.st_mode=S_IRUSR | S_IWUSR;
if (!stat (bakfname, &statbuf) && !chmod (fname, statbuf.st_mode))
;
else
log_error ("WARNING: unable to restore permissions to '%s': %s",
fname, strerror(errno));
}
#endif
return 0;
fail:
if (block)
gnupg_unblock_all_signals ();
return rc;
}
static int
write_keyblock (IOBUF fp, KBNODE keyblock)
{
KBNODE kbctx = NULL, node;
int rc;
while ( (node = walk_kbnode (keyblock, &kbctx, 0)) )
{
if ( (rc = build_packet_and_meta (fp, node->pkt) ))
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
return 0;
}
/*
* Walk over all public keyrings, check the signatures and replace the
* keyring with a new one where the signature cache is then updated.
* This is only done for the public keyrings.
*/
int
keyring_rebuild_cache (ctrl_t ctrl, void *token, int noisy)
{
KEYRING_HANDLE hd;
KEYDB_SEARCH_DESC desc;
KBNODE keyblock = NULL, node;
const char *lastresname = NULL, *resname;
IOBUF tmpfp = NULL;
char *tmpfilename = NULL;
char *bakfilename = NULL;
int rc;
ulong count = 0, sigcount = 0;
hd = keyring_new (token);
if (!hd)
return gpg_error_from_syserror ();
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
rc=keyring_lock (hd, 1);
if(rc)
goto leave;
for (;;)
{
rc = keyring_search (hd, &desc, 1, NULL, 1 /* ignore_legacy */);
if (rc)
break; /* ready. */
desc.mode = KEYDB_SEARCH_MODE_NEXT;
resname = keyring_get_resource_name (hd);
if (lastresname != resname )
{ /* we have switched to a new keyring - commit changes */
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
/* Static analyzer note: BAKFILENAME is never NULL here
because it is controlled by LASTRESNAME. */
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
if (rc)
goto leave;
lastresname = resname;
if (noisy && !opt.quiet)
log_info (_("caching keyring '%s'\n"), resname);
rc = create_tmp_file (resname, &bakfilename, &tmpfilename, &tmpfp);
if (rc)
goto leave;
}
release_kbnode (keyblock);
rc = keyring_get_keyblock (hd, &keyblock);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY)
continue; /* Skip legacy keys. */
log_error ("keyring_get_keyblock failed: %s\n", gpg_strerror (rc));
goto leave;
}
if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
/* We had a few reports about corrupted keyrings; if we have
been called directly from the command line we delete such
a keyblock instead of bailing out. */
log_error ("unexpected keyblock found (pkttype=%d)%s\n",
keyblock->pkt->pkttype, noisy? " - deleted":"");
if (noisy)
continue;
log_info ("Hint: backup your keys and try running '%s'\n",
"gpg --rebuild-keydb-caches");
rc = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (keyblock->pkt->pkt.public_key->version < 4)
{
/* We do not copy/cache v3 keys or any other unknown
packets. It is better to remove them from the keyring.
The code required to keep them in the keyring would be
too complicated. Given that we do not touch the old
secring.gpg a suitable backup for decryption of v3 stuff
using an older gpg version will always be available.
Note: This test is actually superfluous because we
already acted upon GPG_ERR_LEGACY_KEY. */
}
else
{
/* Check all signature to set the signature's cache flags. */
for (node=keyblock; node; node=node->next)
{
/* Note that this doesn't cache the result of a
revocation issued by a designated revoker. This is
because the pk in question does not carry the revkeys
as we haven't merged the key and selfsigs. It is
questionable whether this matters very much since
there are very very few designated revoker revocation
packets out there. */
if (node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig=node->pkt->pkt.signature;
if(!opt.no_sig_cache && sig->flags.checked && sig->flags.valid
&& (openpgp_md_test_algo(sig->digest_algo)
|| openpgp_pk_test_algo(sig->pubkey_algo)))
sig->flags.checked=sig->flags.valid=0;
else
check_key_signature (ctrl, keyblock, node, NULL);
sigcount++;
}
}
/* Write the keyblock to the temporary file. */
rc = write_keyblock (tmpfp, keyblock);
if (rc)
goto leave;
if ( !(++count % 50) && noisy && !opt.quiet)
log_info (ngettext("%lu keys cached so far (%lu signature)\n",
"%lu keys cached so far (%lu signatures)\n",
sigcount),
count, sigcount);
}
} /* end main loop */
if (rc == -1)
rc = 0;
if (rc)
{
log_error ("keyring_search failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (noisy || opt.verbose)
{
log_info (ngettext("%lu key cached",
"%lu keys cached", count), count);
log_printf (ngettext(" (%lu signature)\n",
" (%lu signatures)\n", sigcount), sigcount);
}
if (tmpfp)
{
if (iobuf_close (tmpfp))
{
rc = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n",
tmpfilename, strerror (errno));
goto leave;
}
/* because we have switched resources, we can be sure that
* the original file is closed */
tmpfp = NULL;
}
rc = lastresname? rename_tmp_file (bakfilename, tmpfilename,
lastresname) : 0;
xfree (tmpfilename); tmpfilename = NULL;
xfree (bakfilename); bakfilename = NULL;
leave:
if (tmpfp)
iobuf_cancel (tmpfp);
xfree (tmpfilename);
xfree (bakfilename);
release_kbnode (keyblock);
keyring_lock (hd, 0);
keyring_release (hd);
return rc;
}
/****************
* Perform insert/delete/update operation.
* mode 1 = insert
* 2 = delete
* 3 = update
*/
static int
do_copy (int mode, const char *fname, KBNODE root,
off_t start_offset, unsigned int n_packets )
{
IOBUF fp, newfp;
int rc=0;
char *bakfname = NULL;
char *tmpfname = NULL;
/* Open the source file. Because we do a rename, we have to check the
permissions of the file */
if (access (fname, W_OK))
return gpg_error_from_syserror ();
fp = iobuf_open (fname);
if (mode == 1 && !fp && errno == ENOENT) {
/* insert mode but file does not exist: create a new file */
KBNODE kbctx, node;
mode_t oldmask;
oldmask=umask(077);
if (is_secured_filename (fname)) {
newfp = NULL;
gpg_err_set_errno (EPERM);
}
else
newfp = iobuf_create (fname, 1);
umask(oldmask);
if( !newfp )
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, strerror(errno));
return rc;
}
if( !opt.quiet )
log_info(_("%s: keyring created\n"), fname );
kbctx=NULL;
while ( (node = walk_kbnode( root, &kbctx, 0 )) ) {
if( (rc = build_packet( newfp, node->pkt )) ) {
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
iobuf_cancel(newfp);
return rc;
}
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error ("%s: close failed: %s\n", fname, strerror(errno));
return rc;
}
return 0; /* ready */
}
if( !fp )
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), fname, strerror(errno) );
goto leave;
}
/* Create the new file. */
rc = create_tmp_file (fname, &bakfname, &tmpfname, &newfp);
if (rc) {
iobuf_close(fp);
goto leave;
}
if( mode == 1 ) { /* insert */
/* copy everything to the new file */
rc = copy_all_packets (fp, newfp);
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy first part to the new file */
rc = copy_some_packets( fp, newfp, start_offset );
if( rc ) { /* should never get EOF here */
log_error ("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
/* skip this keyblock */
log_assert( n_packets );
rc = skip_some_packets( fp, n_packets );
if( rc ) {
log_error("%s: skipping %u packets failed: %s\n",
fname, n_packets, gpg_strerror (rc));
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 1 || mode == 3 ) { /* insert or update */
rc = write_keyblock (newfp, root);
if (rc) {
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
if( mode == 2 || mode == 3 ) { /* delete or update */
/* copy the rest */
rc = copy_all_packets( fp, newfp );
if( rc != -1 ) {
log_error("%s: copy to '%s' failed: %s\n",
fname, tmpfname, gpg_strerror (rc) );
iobuf_close(fp);
iobuf_cancel(newfp);
goto leave;
}
}
/* close both files */
if( iobuf_close(fp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", fname, strerror(errno) );
goto leave;
}
if( iobuf_close(newfp) ) {
rc = gpg_error_from_syserror ();
log_error("%s: close failed: %s\n", tmpfname, strerror(errno) );
goto leave;
}
rc = rename_tmp_file (bakfname, tmpfname, fname);
leave:
xfree(bakfname);
xfree(tmpfname);
return rc;
}
diff --git a/g10/objcache.c b/g10/objcache.c
index a90b4d9d8..8d9ccefca 100644
--- a/g10/objcache.c
+++ b/g10/objcache.c
@@ -1,689 +1,689 @@
/* objcache.c - Caching functions for keys and user ids.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "options.h"
#include "objcache.h"
/* Note that max value for uid_items is actually the threshold when
* we start to look for items which can be removed. */
#define NO_OF_UID_ITEM_BUCKETS 107
#define MAX_UID_ITEMS_PER_BUCKET 20
#define NO_OF_KEY_ITEM_BUCKETS 383
#define MAX_KEY_ITEMS_PER_BUCKET 20
/* An object to store a user id. This describes an item in the linked
* lists of a bucket in hash table. The reference count will
* eventually be used to remove items from the table. */
typedef struct uid_item_s
{
struct uid_item_s *next;
unsigned int refcount; /* The reference count for this item. */
unsigned int namelen; /* The length of the UID sans the nul. */
char name[1];
} *uid_item_t;
static uid_item_t *uid_table; /* Hash table for with user ids. */
static size_t uid_table_size; /* Number of allocated buckets. */
static unsigned int uid_table_max; /* Max. # of items in a bucket. */
static unsigned int uid_table_added; /* # of items added. */
static unsigned int uid_table_dropped;/* # of items dropped. */
/* An object to store properties of a key. Note that this can be used
* for a primary or a subkey. The key is linked to a user if that
* exists. */
typedef struct key_item_s
{
struct key_item_s *next;
unsigned int usecount;
byte fprlen;
char fpr[MAX_FINGERPRINT_LEN];
u32 keyid[2];
uid_item_t ui; /* NULL of a ref'ed user id item. */
} *key_item_t;
static key_item_t *key_table; /* Hash table with the keys. */
static size_t key_table_size; /* Number of allocated buckents. */
static unsigned int key_table_max; /* Max. # of items in a bucket. */
static unsigned int key_table_added; /* # of items added. */
static unsigned int key_table_dropped;/* # of items dropped. */
static key_item_t key_item_attic; /* List of freed items. */
/* Dump stats. */
void
objcache_dump_stats (void)
{
unsigned int idx;
int len, minlen, maxlen;
unsigned int count, attic, empty;
key_item_t ki;
uid_item_t ui;
count = empty = 0;
minlen = -1;
maxlen = 0;
for (idx = 0; idx < key_table_size; idx++)
{
len = 0;
for (ki = key_table[idx]; ki; ki = ki->next)
{
count++;
len++;
/* log_debug ("key bucket %u: kid=%08lX used=%u ui=%p\n", */
/* idx, (ulong)ki->keyid[0], ki->usecount, ki->ui); */
}
if (len > maxlen)
maxlen = len;
if (!len)
empty++;
else if (minlen == -1 || len < minlen)
minlen = len;
}
for (attic=0, ki = key_item_attic; ki; ki = ki->next)
attic++;
log_info ("objcache: keys=%u/%u/%u chains=%u,%d..%d buckets=%zu/%u"
" attic=%u\n",
count, key_table_added, key_table_dropped,
empty, minlen > 0? minlen : 0, maxlen,
key_table_size, key_table_max, attic);
count = empty = 0;
minlen = -1;
maxlen = 0;
for (idx = 0; idx < uid_table_size; idx++)
{
len = 0;
for (ui = uid_table[idx]; ui; ui = ui->next)
{
count++;
len++;
/* log_debug ("uid bucket %u: %p ref=%u l=%u (%.20s)\n", */
/* idx, ui, ui->refcount, ui->namelen, ui->name); */
}
if (len > maxlen)
maxlen = len;
if (!len)
empty++;
else if (minlen == -1 || len < minlen)
minlen = len;
}
log_info ("objcache: uids=%u/%u/%u chains=%u,%d..%d buckets=%zu/%u\n",
count, uid_table_added, uid_table_dropped,
empty, minlen > 0? minlen : 0, maxlen,
uid_table_size, uid_table_max);
}
/* The hash function we use for the uid_table. Must not call a system
* function. */
static inline unsigned int
uid_table_hasher (const char *name, unsigned namelen)
{
const unsigned char *s = (const unsigned char*)name;
unsigned int hashval = 0;
unsigned int carry;
for (; namelen; namelen--, s++)
{
hashval = (hashval << 4) + *s;
if ((carry = (hashval & 0xf0000000)))
{
hashval ^= (carry >> 24);
hashval ^= carry;
}
}
return hashval % uid_table_size;
}
/* Run time allocation of the uid table. This allows us to eventually
* add an option to gpg to control the size. */
static void
uid_table_init (void)
{
if (uid_table)
return;
uid_table_size = NO_OF_UID_ITEM_BUCKETS;
uid_table_max = MAX_UID_ITEMS_PER_BUCKET;
uid_table = xcalloc (uid_table_size, sizeof *uid_table);
}
static uid_item_t
uid_item_ref (uid_item_t ui)
{
if (ui)
ui->refcount++;
return ui;
}
static void
uid_item_unref (uid_item_t uid)
{
if (!uid)
return;
if (!uid->refcount)
log_fatal ("too many unrefs for uid_item\n");
uid->refcount--;
/* We do not release the item here because that would require that
* we locate the head of the list which has this item. This will
* take too long and thus the item is removed when we need to purge
* some items for the list during uid_item_put. */
}
/* Put (NAME,NAMELEN) into the UID_TABLE and return the item. The
* reference count for that item is incremented. NULL is return on an
* allocation error. The caller should release the returned item
* using uid_item_unref. */
static uid_item_t
uid_table_put (const char *name, unsigned int namelen)
{
unsigned int hash;
uid_item_t ui;
unsigned int count;
if (!uid_table)
uid_table_init ();
hash = uid_table_hasher (name, namelen);
for (ui = uid_table[hash], count = 0; ui; ui = ui->next, count++)
if (ui->namelen == namelen && !memcmp (ui->name, name, namelen))
return uid_item_ref (ui); /* Found. */
/* If the bucket is full remove all unrefed items. */
if (count >= uid_table_max)
{
uid_item_t ui_next, ui_prev, list_head, drop_head;
/* No syscalls from here .. */
list_head = uid_table[hash];
drop_head = NULL;
while (list_head && !list_head->refcount)
{
ui = list_head;
list_head = ui->next;
ui->next = drop_head;
drop_head = ui;
}
if ((ui_prev = list_head))
for (ui = ui_prev->next; ui; ui = ui_next)
{
ui_next = ui->next;
if (!ui->refcount)
{
ui->next = drop_head;
drop_head = ui;
ui_prev->next = ui_next;
}
else
ui_prev = ui;
}
uid_table[hash] = list_head;
/* ... to here */
for (ui = drop_head; ui; ui = ui_next)
{
ui_next = ui->next;
xfree (ui);
uid_table_dropped++;
}
}
count = uid_table_added + uid_table_dropped;
ui = xtrycalloc (1, sizeof *ui + namelen);
if (!ui)
return NULL; /* Out of core. */
if (count != uid_table_added + uid_table_dropped)
{
/* During the malloc another thread added an item. Thus we need
* to check again. */
uid_item_t ui_new = ui;
for (ui = uid_table[hash]; ui; ui = ui->next)
if (ui->namelen == namelen && !memcmp (ui->name, name, namelen))
{
/* Found. */
xfree (ui_new);
return uid_item_ref (ui);
}
ui = ui_new;
}
memcpy (ui->name, name, namelen);
ui->name[namelen] = 0; /* Extra Nul so we can use it as a string. */
ui->namelen = namelen;
ui->refcount = 1;
ui->next = uid_table[hash];
uid_table[hash] = ui;
uid_table_added++;
return ui;
}
/* The hash function we use for the key_table. Must not call a system
* function. */
static inline unsigned int
key_table_hasher (u32 *keyid)
{
/* A fingerprint could be used directly as a hash value. However,
* we use the keyid here because it is used in encrypted packets and
* older signatures to identify a key. Since v4 keys the keyid is
* anyway a part of the fingerprint so it quickly extracted from a
* fingerprint. Note that v3 keys are not supported by gpg. */
return keyid[0] % key_table_size;
}
/* Run time allocation of the key table. This allows us to eventually
* add an option to gpg to control the size. */
static void
key_table_init (void)
{
if (key_table)
return;
key_table_size = NO_OF_KEY_ITEM_BUCKETS;
key_table_max = MAX_KEY_ITEMS_PER_BUCKET;
key_table = xcalloc (key_table_size, sizeof *key_table);
}
static void
key_item_free (key_item_t ki)
{
if (!ki)
return;
uid_item_unref (ki->ui);
ki->ui = NULL;
ki->next = key_item_attic;
key_item_attic = ki;
}
/* Get a key item from PK or if that is NULL from KEYID. The
* reference count for that item is incremented. NULL is return if it
* was not found. */
static key_item_t
key_table_get (PKT_public_key *pk, u32 *keyid)
{
unsigned int hash;
key_item_t ki, ki2;
if (!key_table)
key_table_init ();
if (pk)
{
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
u32 tmpkeyid[2];
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, tmpkeyid);
hash = key_table_hasher (tmpkeyid);
for (ki = key_table[hash]; ki; ki = ki->next)
if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
return ki; /* Found */
}
else if (keyid)
{
hash = key_table_hasher (keyid);
for (ki = key_table[hash]; ki; ki = ki->next)
if (ki->keyid[0] == keyid[0] && ki->keyid[1] == keyid[1])
{
/* Found. We need to check for dups. */
for (ki2 = ki->next; ki2; ki2 = ki2->next)
if (ki2->keyid[0] == keyid[0] && ki2->keyid[1] == keyid[1])
- return NULL; /* Duplicated keyid - retrun NULL. */
+ return NULL; /* Duplicated keyid - return NULL. */
/* This is the only one - return it. */
return ki;
}
}
return NULL;
}
/* Helper for the qsort in key_table_put. */
static int
compare_key_items (const void *arg_a, const void *arg_b)
{
const key_item_t a = *(const key_item_t *)arg_a;
const key_item_t b = *(const key_item_t *)arg_b;
/* Reverse sort on the usecount. */
if (a->usecount > b->usecount)
return -1;
else if (a->usecount == b->usecount)
return 0;
else
return 1;
}
/* Put PK into the KEY_TABLE and return a key item. The reference
* count for that item is incremented. If UI is given it is put into
* the entry. NULL is return on an allocation error. */
static key_item_t
key_table_put (PKT_public_key *pk, uid_item_t ui)
{
unsigned int hash;
key_item_t ki;
u32 keyid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
unsigned int count, n;
if (!key_table)
key_table_init ();
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, keyid);
hash = key_table_hasher (keyid);
for (ki = key_table[hash], count=0; ki; ki = ki->next, count++)
if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
return ki; /* Found */
/* If the bucket is full remove a couple of items. */
if (count >= key_table_max)
{
key_item_t list_head, *list_tailp, ki_next;
key_item_t *array;
int narray, idx;
/* Unlink from the global list so that other threads don't
* disturb us. If another thread adds or removes something only
* one will be the winner. Bad luck for the drooped cache items
* but after all it is just a cache. */
list_head = key_table[hash];
key_table[hash] = NULL;
/* Put all items into an array for sorting. */
array = xtrycalloc (count, sizeof *array);
if (!array)
{
/* That's bad; give up all items of the bucket. */
log_info ("Note: malloc failed while purging from the key_tabe: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
goto leave_drop;
}
narray = 0;
for (ki = list_head; ki; ki = ki_next)
{
ki_next = ki->next;
array[narray++] = ki;
ki->next = NULL;
}
log_assert (narray == count);
/* Sort the array and put half of it onto a new list. */
qsort (array, narray, sizeof *array, compare_key_items);
list_head = NULL;
list_tailp = &list_head;
for (idx=0; idx < narray/2; idx++)
{
*list_tailp = array[idx];
list_tailp = &array[idx]->next;
}
/* Put the new list into the bucket. */
ki = key_table[hash];
key_table[hash] = list_head;
list_head = ki;
/* Free the remaining items and the array. */
for (; idx < narray; idx++)
{
key_item_free (array[idx]);
key_table_dropped++;
}
xfree (array);
leave_drop:
/* Free any items added in the meantime by other threads. This
* is also used in case of a malloc problem (which won't update
* the counters, though). */
for ( ; list_head; list_head = ki_next)
{
ki_next = list_head->next;
key_item_free (list_head);
}
}
/* Add an item to the bucket. We allocate a whole block of items
- * for cache performace reasons. */
+ * for cache performance reasons. */
if (!key_item_attic)
{
key_item_t kiblock;
int kiblocksize = 256;
kiblock = xtrymalloc (kiblocksize * sizeof *kiblock);
if (!kiblock)
return NULL; /* Out of core. */
for (n = 0; n < kiblocksize; n++)
{
ki = kiblock + n;
ki->next = key_item_attic;
key_item_attic = ki;
}
/* During the malloc another thread may have changed the bucket.
* Thus we need to check again. */
for (ki = key_table[hash]; ki; ki = ki->next)
if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
return ki; /* Found */
}
/* We now know that there is an item in the attic. */
ki = key_item_attic;
key_item_attic = ki->next;
ki->next = NULL;
memcpy (ki->fpr, fpr, fprlen);
ki->fprlen = fprlen;
ki->keyid[0] = keyid[0];
ki->keyid[1] = keyid[1];
ki->ui = uid_item_ref (ui);
ki->usecount = 0;
ki->next = key_table[hash];
key_table[hash] = ki;
key_table_added++;
return ki;
}
/* Return the user ID from the given keyblock. We use the primary uid
* flag which should have already been set. The returned value is
* only valid as long as the given keyblock is not changed. */
static const char *
primary_uid_from_keyblock (kbnode_t keyblock, size_t *uidlen)
{
kbnode_t k;
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->flags.primary)
{
*uidlen = k->pkt->pkt.user_id->len;
return k->pkt->pkt.user_id->name;
}
}
return NULL;
}
/* Store the associations of keyid/fingerprint and userid. Only
* public keys should be fed to this function. */
void
cache_put_keyblock (kbnode_t keyblock)
{
uid_item_t ui = NULL;
kbnode_t k;
restart:
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (!ui)
{
/* Initially we just test for an entry to avoid the need
* to create a user id item for a put. Only if we miss
* key in the cache we create a user id and restart. */
if (!key_table_get (k->pkt->pkt.public_key, NULL))
{
const char *uid;
size_t uidlen;
uid = primary_uid_from_keyblock (keyblock, &uidlen);
if (uid)
{
ui = uid_table_put (uid, uidlen);
if (!ui)
{
log_info ("Note: failed to cache a user id: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
goto leave;
}
goto restart;
}
}
}
else /* With a UID we use the update cache mode. */
{
if (!key_table_put (k->pkt->pkt.public_key, ui))
{
log_info ("Note: failed to cache a key: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
goto leave;
}
}
}
}
leave:
uid_item_unref (ui);
}
/* Return the user id string for KEYID. If a user id is not found (or
* on malloc error) NULL is returned. If R_LENGTH is not NULL the
* length of the user id is stored there; this does not included the
* always appended nul. Note that a user id may include an internal
* nul which can be detected by the caller by comparing to the
* returned length. */
char *
cache_get_uid_bykid (u32 *keyid, unsigned int *r_length)
{
key_item_t ki;
char *p;
if (r_length)
*r_length = 0;
ki = key_table_get (NULL, keyid);
if (!ki)
return NULL; /* Not found or duplicate keyid. */
if (!ki->ui)
p = NULL; /* No user id known for key. */
else
{
p = xtrymalloc (ki->ui->namelen + 1);
if (p)
{
memcpy (p, ki->ui->name, ki->ui->namelen + 1);
if (r_length)
*r_length = ki->ui->namelen;
ki->usecount++;
}
}
return p;
}
/* Return the user id string for FPR with FPRLEN. If a user id is not
* found (or on malloc error) NULL is returned. If R_LENGTH is not
* NULL the length of the user id is stored there; this does not
* included the always appended nul. Note that a user id may include
* an internal nul which can be detected by the caller by comparing to
* the returned length. */
char *
cache_get_uid_byfpr (const byte *fpr, size_t fprlen, size_t *r_length)
{
char *p;
unsigned int hash;
u32 keyid[2];
key_item_t ki;
if (r_length)
*r_length = 0;
if (!key_table)
return NULL;
keyid_from_fingerprint (NULL, fpr, fprlen, keyid);
hash = key_table_hasher (keyid);
for (ki = key_table[hash]; ki; ki = ki->next)
if (ki->fprlen == fprlen && !memcmp (ki->fpr, fpr, fprlen))
break; /* Found */
if (!ki)
return NULL; /* Not found. */
if (!ki->ui)
p = NULL; /* No user id known for key. */
else
{
p = xtrymalloc (ki->ui->namelen + 1);
if (p)
{
memcpy (p, ki->ui->name, ki->ui->namelen + 1);
if (r_length)
*r_length = ki->ui->namelen;
ki->usecount++;
}
}
return p;
}
diff --git a/g10/tdbio.c b/g10/tdbio.c
index b6c38bd24..bfeede991 100644
--- a/g10/tdbio.c
+++ b/g10/tdbio.c
@@ -1,1931 +1,1931 @@
/* tdbio.c - trust database I/O operations
* Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc.
* Copyright (C) 1998-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../common/util.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#include "trustdb.h"
#include "tdbio.h"
#if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate)
#define ftruncate chsize
#endif
#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY O_BINARY
#else
#define MY_O_BINARY 0
#endif
/* We use ERRNO despite that the cegcc provided open/read/write
functions don't set ERRNO - at least show that ERRNO does not make
sense. */
#ifdef HAVE_W32CE_SYSTEM
#undef strerror
#define strerror(a) ("[errno not available]")
#endif
/*
* Yes, this is a very simple implementation. We should really
* use a page aligned buffer and read complete pages.
* To implement a simple trannsaction system, this is sufficient.
*/
typedef struct cache_ctrl_struct *CACHE_CTRL;
struct cache_ctrl_struct
{
CACHE_CTRL next;
struct {
unsigned used:1;
unsigned dirty:1;
} flags;
ulong recno;
char data[TRUST_RECORD_LEN];
};
/* Size of the cache. The SOFT value is the general one. While in a
transaction this may not be sufficient and thus we may increase it
then up to the HARD limit. */
#define MAX_CACHE_ENTRIES_SOFT 200
#define MAX_CACHE_ENTRIES_HARD 10000
/* The cache is controlled by these variables. */
static CACHE_CTRL cache_list;
static int cache_entries;
static int cache_is_dirty;
/* An object to pass information to cmp_krec_fpr. */
struct cmp_krec_fpr_struct
{
int pubkey_algo;
const char *fpr;
int fprlen;
};
/* An object used to pass information to cmp_[s]dir. */
struct cmp_xdir_struct
{
int pubkey_algo;
u32 keyid[2];
};
/* The name of the trustdb file. */
static char *db_name;
/* The handle for locking the trustdb file and a counter to record how
* often this lock has been taken. That counter is required because
- * dotlock does not implemen recursive locks. */
+ * dotlock does not implement recursive locks. */
static dotlock_t lockhandle;
static unsigned int is_locked;
/* The file descriptor of the trustdb. */
static int db_fd = -1;
/* A flag indicating that a transaction is active. */
/* static int in_transaction; Not yet used. */
static void open_db (void);
static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type);
/*
* Take a lock on the trustdb file name. I a lock file can't be
* created the function terminates the process. Except for a
* different return code the function does nothing if the lock has
* already been taken.
*
* Returns: True if lock already exists, False if the lock has
* actually been taken.
*/
static int
take_write_lock (void)
{
int rc;
if (!lockhandle)
lockhandle = dotlock_create (db_name, 0);
if (!lockhandle)
log_fatal ( _("can't create lock for '%s'\n"), db_name );
if (!is_locked)
{
if (dotlock_take (lockhandle, -1) )
log_fatal ( _("can't lock '%s'\n"), db_name );
rc = 0;
}
else
rc = 1;
if (opt.lock_once)
is_locked = 1;
else
is_locked++;
return rc;
}
/*
* Release a lock from the trustdb file unless the global option
* --lock-once has been used.
*/
static void
release_write_lock (void)
{
if (opt.lock_once)
return; /* Don't care; here IS_LOCKED is fixed to 1. */
if (!is_locked)
{
log_error ("Ooops, tdbio:release_write_lock with no lock held\n");
return;
}
if (--is_locked)
return;
if (dotlock_release (lockhandle))
log_error ("Oops, tdbio:release_write_locked failed\n");
}
/*************************************
************* record cache **********
*************************************/
/*
* Get the data from the record cache and return a pointer into that
* cache. Caller should copy the returned data. NULL is returned on
* a cache miss.
*/
static const char *
get_record_from_cache (ulong recno)
{
CACHE_CTRL r;
for (r = cache_list; r; r = r->next)
{
if (r->flags.used && r->recno == recno)
return r->data;
}
return NULL;
}
/*
* Write a cached item back to the trustdb file.
*
* Returns: 0 on success or an error code.
*/
static int
write_cache_item (CACHE_CTRL r)
{
gpg_error_t err;
int n;
if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: lseek failed: %s\n"),
r->recno, strerror (errno));
return err;
}
n = write (db_fd, r->data, TRUST_RECORD_LEN);
if (n != TRUST_RECORD_LEN)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
r->recno, n, strerror (errno) );
return err;
}
r->flags.dirty = 0;
return 0;
}
/*
* Put data into the cache. This function may flush
* some cache entries if the cache is filled up.
*
* Returns: 0 on success or an error code.
*/
static int
put_record_into_cache (ulong recno, const char *data)
{
CACHE_CTRL r, unused;
int dirty_count = 0;
int clean_count = 0;
/* See whether we already cached this one. */
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (!r->flags.used)
{
if (!unused)
unused = r;
}
else if (r->recno == recno)
{
if (!r->flags.dirty)
{
/* Hmmm: should we use a copy and compare? */
if (memcmp (r->data, data, TRUST_RECORD_LEN))
{
r->flags.dirty = 1;
cache_is_dirty = 1;
}
}
memcpy (r->data, data, TRUST_RECORD_LEN);
return 0;
}
if (r->flags.used)
{
if (r->flags.dirty)
dirty_count++;
else
clean_count++;
}
}
/* Not in the cache: add a new entry. */
if (unused)
{
/* Reuse this entry. */
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* See whether we reached the limit. */
if (cache_entries < MAX_CACHE_ENTRIES_SOFT)
{
/* No: Put into cache. */
r = xmalloc (sizeof *r);
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
r->next = cache_list;
cache_list = r;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* Cache is full: discard some clean entries. */
if (clean_count)
{
int n;
/* We discard a third of the clean entries. */
n = clean_count / 3;
if (!n)
n = 1;
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (r->flags.used && !r->flags.dirty)
{
if (!unused)
unused = r;
r->flags.used = 0;
cache_entries--;
if (!--n)
break;
}
}
/* Now put into the cache. */
log_assert (unused);
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* No clean entries: We have to flush some dirty entries. */
#if 0 /* Transactions are not yet used. */
if (in_transaction)
{
/* But we can't do this while in a transaction. Thus we
* increase the cache size instead. */
if (cache_entries < MAX_CACHE_ENTRIES_HARD)
{
if (opt.debug && !(cache_entries % 100))
log_debug ("increasing tdbio cache size\n");
r = xmalloc (sizeof *r);
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
r->next = cache_list;
cache_list = r;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* Hard limit for the cache size reached. */
log_info (_("trustdb transaction too large\n"));
return GPG_ERR_RESOURCE_LIMIT;
}
#endif
if (dirty_count)
{
int n;
/* Discard some dirty entries. */
n = dirty_count / 5;
if (!n)
n = 1;
take_write_lock ();
for (unused = NULL, r = cache_list; r; r = r->next)
{
if (r->flags.used && r->flags.dirty)
{
int rc;
rc = write_cache_item (r);
if (rc)
return rc;
if (!unused)
unused = r;
r->flags.used = 0;
cache_entries--;
if (!--n)
break;
}
}
release_write_lock ();
/* Now put into the cache. */
log_assert (unused);
r = unused;
r->flags.used = 1;
r->recno = recno;
memcpy (r->data, data, TRUST_RECORD_LEN);
r->flags.dirty = 1;
cache_is_dirty = 1;
cache_entries++;
return 0;
}
/* We should never reach this. */
BUG();
}
/* Return true if the cache is dirty. */
int
tdbio_is_dirty()
{
return cache_is_dirty;
}
/*
* Flush the cache. This cannot be used while in a transaction.
*/
int
tdbio_sync()
{
CACHE_CTRL r;
int did_lock = 0;
if( db_fd == -1 )
open_db();
#if 0 /* Transactions are not yet used. */
if( in_transaction )
log_bug("tdbio: syncing while in transaction\n");
#endif
if( !cache_is_dirty )
return 0;
if (!take_write_lock ())
did_lock = 1;
for( r = cache_list; r; r = r->next ) {
if( r->flags.used && r->flags.dirty ) {
int rc = write_cache_item( r );
if( rc )
return rc;
}
}
cache_is_dirty = 0;
if (did_lock)
release_write_lock ();
return 0;
}
#if 0 /* Not yet used. */
/*
* Simple transactions system:
* Everything between begin_transaction and end/cancel_transaction
* is not immediately written but at the time of end_transaction.
*
* NOTE: The transaction code is disabled in the 1.2 branch, as it is
* not yet used.
*/
int
tdbio_begin_transaction () /* Not yet used. */
{
int rc;
if (in_transaction)
log_bug ("tdbio: nested transactions\n");
/* Flush everything out. */
rc = tdbio_sync();
if (rc)
return rc;
in_transaction = 1;
return 0;
}
int
tdbio_end_transaction () /* Not yet used. */
{
int rc;
if (!in_transaction)
log_bug ("tdbio: no active transaction\n");
take_write_lock ();
gnupg_block_all_signals ();
in_transaction = 0;
rc = tdbio_sync();
gnupg_unblock_all_signals();
release_write_lock ();
return rc;
}
int
tdbio_cancel_transaction () /* Not yet used. */
{
CACHE_CTRL r;
if (!in_transaction)
log_bug ("tdbio: no active transaction\n");
/* Remove all dirty marked entries, so that the original ones are
* read back the next time. */
if (cache_is_dirty)
{
for (r = cache_list; r; r = r->next)
{
if (r->flags.used && r->flags.dirty)
{
r->flags.used = 0;
cache_entries--;
}
}
cache_is_dirty = 0;
}
in_transaction = 0;
return 0;
}
#endif /* Not yet used. */
/********************************************************
**************** cached I/O functions ******************
********************************************************/
/* The cleanup handler for this module. */
static void
cleanup (void)
{
if (is_locked)
{
if (!dotlock_release (lockhandle))
is_locked = 0;
}
}
/*
* Update an existing trustdb record. The caller must call
* tdbio_sync.
*
* Returns: 0 on success or an error code.
*/
int
tdbio_update_version_record (ctrl_t ctrl)
{
TRUSTREC rec;
int rc;
int opt_tm;
/* Never store a TOFU trust model in the trustdb. Use PGP instead. */
opt_tm = opt.trust_model;
if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
opt_tm = TM_PGP;
memset (&rec, 0, sizeof rec);
rc = tdbio_read_record (0, &rec, RECTYPE_VER);
if (!rc)
{
rec.r.ver.created = make_timestamp();
rec.r.ver.marginals = opt.marginals_needed;
rec.r.ver.completes = opt.completes_needed;
rec.r.ver.cert_depth = opt.max_cert_depth;
rec.r.ver.trust_model = opt_tm;
rec.r.ver.min_cert_level = opt.min_cert_level;
rc = tdbio_write_record (ctrl, &rec);
}
return rc;
}
/*
* Create and write the trustdb version record.
* This is called with the writelock active.
* Returns: 0 on success or an error code.
*/
static int
create_version_record (ctrl_t ctrl)
{
TRUSTREC rec;
int rc;
int opt_tm;
/* Never store a TOFU trust model in the trustdb. Use PGP instead. */
opt_tm = opt.trust_model;
if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
opt_tm = TM_PGP;
memset (&rec, 0, sizeof rec);
rec.r.ver.version = 3;
rec.r.ver.created = make_timestamp ();
rec.r.ver.marginals = opt.marginals_needed;
rec.r.ver.completes = opt.completes_needed;
rec.r.ver.cert_depth = opt.max_cert_depth;
if (opt_tm == TM_PGP || opt_tm == TM_CLASSIC)
rec.r.ver.trust_model = opt_tm;
else
rec.r.ver.trust_model = TM_PGP;
rec.r.ver.min_cert_level = opt.min_cert_level;
rec.rectype = RECTYPE_VER;
rec.recnum = 0;
rc = tdbio_write_record (ctrl, &rec);
if (!rc)
tdbio_sync ();
if (!rc)
create_hashtable (ctrl, &rec, 0);
return rc;
}
/*
* Set the file name for the trustdb to NEW_DBNAME and if CREATE is
* true create that file. If NEW_DBNAME is NULL a default name is
* used, if the it does not contain a path component separator ('/')
* the global GnuPG home directory is used.
*
* Returns: 0 on success or an error code.
*
* On the first call this function registers an atexit handler.
*
*/
int
tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname,
int create, int *r_nofile)
{
char *fname, *p;
struct stat statbuf;
static int initialized = 0;
int save_slash;
if (!initialized)
{
atexit (cleanup);
initialized = 1;
}
*r_nofile = 0;
if (!new_dbname)
{
fname = make_filename (gnupg_homedir (),
"trustdb" EXTSEP_S GPGEXT_GPG, NULL);
}
else if (*new_dbname != DIRSEP_C )
{
if (strchr (new_dbname, DIRSEP_C))
fname = make_filename (new_dbname, NULL);
else
fname = make_filename (gnupg_homedir (), new_dbname, NULL);
}
else
{
fname = xstrdup (new_dbname);
}
xfree (db_name);
db_name = fname;
/* Quick check for (likely) case where there already is a
* trustdb.gpg. This check is not required in theory, but it helps
* in practice avoiding costly operations of preparing and taking
* the lock. */
if (!stat (fname, &statbuf) && statbuf.st_size > 0)
{
/* OK, we have the valid trustdb.gpg already. */
return 0;
}
else if (!create)
{
*r_nofile = 1;
return 0;
}
/* Here comes: No valid trustdb.gpg AND CREATE==1 */
/*
* Make sure the directory exists. This should be done before
* acquiring the lock, which assumes the existence of the directory.
*/
p = strrchr (fname, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take
care of it. */
char *pp = strrchr (fname, '/');
if (!p || pp > p)
p = pp;
}
#endif /*HAVE_W32_SYSTEM*/
log_assert (p);
save_slash = *p;
*p = 0;
if (access (fname, F_OK))
{
try_make_homedir (fname);
if (access (fname, F_OK))
log_fatal (_("%s: directory does not exist!\n"), fname);
}
*p = save_slash;
take_write_lock ();
if (access (fname, R_OK) || stat (fname, &statbuf) || statbuf.st_size == 0)
{
FILE *fp;
TRUSTREC rec;
int rc;
mode_t oldmask;
#ifdef HAVE_W32CE_SYSTEM
/* We know how the cegcc implementation of access works ;-). */
if (GetLastError () == ERROR_FILE_NOT_FOUND)
gpg_err_set_errno (ENOENT);
else
gpg_err_set_errno (EIO);
#endif /*HAVE_W32CE_SYSTEM*/
if (errno && errno != ENOENT)
log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno));
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = fopen (fname, "wb");
umask(oldmask);
if (!fp)
log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno));
fclose (fp);
db_fd = open (db_name, O_RDWR | MY_O_BINARY);
if (db_fd == -1)
log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno));
rc = create_version_record (ctrl);
if (rc)
log_fatal (_("%s: failed to create version record: %s"),
fname, gpg_strerror (rc));
/* Read again to check that we are okay. */
if (tdbio_read_record (0, &rec, RECTYPE_VER))
log_fatal (_("%s: invalid trustdb created\n"), db_name);
if (!opt.quiet)
log_info (_("%s: trustdb created\n"), db_name);
}
release_write_lock ();
return 0;
}
/*
* Return the full name of the trustdb.
*/
const char *
tdbio_get_dbname ()
{
return db_name;
}
/*
* Open the trustdb. This may only be called if it has not yet been
* opened and after a successful call to tdbio_set_dbname. On return
* the trustdb handle (DB_FD) is guaranteed to be open.
*/
static void
open_db ()
{
TRUSTREC rec;
log_assert( db_fd == -1 );
#ifdef HAVE_W32CE_SYSTEM
{
DWORD prevrc = 0;
wchar_t *wname = utf8_to_wchar (db_name);
if (wname)
{
db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
xfree (wname);
}
if (db_fd == -1)
log_fatal ("can't open '%s': %d, %d\n", db_name,
(int)prevrc, (int)GetLastError ());
}
#else /*!HAVE_W32CE_SYSTEM*/
db_fd = open (db_name, O_RDWR | MY_O_BINARY );
if (db_fd == -1 && (errno == EACCES
#ifdef EROFS
|| errno == EROFS
#endif
)
) {
/* Take care of read-only trustdbs. */
db_fd = open (db_name, O_RDONLY | MY_O_BINARY );
if (db_fd != -1 && !opt.quiet)
log_info (_("Note: trustdb not writable\n"));
}
if ( db_fd == -1 )
log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) );
#endif /*!HAVE_W32CE_SYSTEM*/
register_secured_file (db_name);
/* Read the version record. */
if (tdbio_read_record (0, &rec, RECTYPE_VER ) )
log_fatal( _("%s: invalid trustdb\n"), db_name );
}
/*
* Append a new empty hashtable to the trustdb. TYPE gives the type
* of the hash table. The only defined type is 0 for a trust hash.
* On return the hashtable has been created, written, the version
* record update, and the data flushed to the disk. On a fatal error
* the function terminates the process.
*/
static void
create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type)
{
TRUSTREC rec;
off_t offset;
ulong recnum;
int i, n, rc;
offset = lseek (db_fd, 0, SEEK_END);
if (offset == -1)
log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno));
recnum = offset / TRUST_RECORD_LEN;
log_assert (recnum); /* This is will never be the first record. */
if (!type)
vr->r.ver.trusthashtbl = recnum;
/* Now write the records making up the hash table. */
n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD;
for (i=0; i < n; i++, recnum++)
{
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HTBL;
rec.recnum = recnum;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_fatal (_("%s: failed to create hashtable: %s\n"),
db_name, gpg_strerror (rc));
}
/* Update the version record and flush. */
rc = tdbio_write_record (ctrl, vr);
if (!rc)
rc = tdbio_sync ();
if (rc)
log_fatal (_("%s: error updating version record: %s\n"),
db_name, gpg_strerror (rc));
}
/*
* Check whether open trustdb matches the global trust options given
* for this process. On a read problem the process is terminated.
*
* Return: 1 for yes, 0 for no.
*/
int
tdbio_db_matches_options()
{
static int yes_no = -1;
if (yes_no == -1)
{
TRUSTREC vr;
int rc;
int opt_tm, tm;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if( rc )
log_fatal( _("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
/* Consider tofu and pgp the same. */
tm = vr.r.ver.trust_model;
if (tm == TM_TOFU || tm == TM_TOFU_PGP)
tm = TM_PGP;
opt_tm = opt.trust_model;
if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
opt_tm = TM_PGP;
yes_no = vr.r.ver.marginals == opt.marginals_needed
&& vr.r.ver.completes == opt.completes_needed
&& vr.r.ver.cert_depth == opt.max_cert_depth
&& tm == opt_tm
&& vr.r.ver.min_cert_level == opt.min_cert_level;
}
return yes_no;
}
/*
* Read and return the trust model identifier from the trustdb. On a
* read problem the process is terminated.
*/
byte
tdbio_read_model (void)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER );
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
return vr.r.ver.trust_model;
}
/*
* Read and return the nextstamp value from the trustdb. On a read
* problem the process is terminated.
*/
ulong
tdbio_read_nextcheck ()
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
return vr.r.ver.nextcheck;
}
/*
* Write the STAMP nextstamp timestamp to the trustdb. On a read or
* write problem the process is terminated.
*
* Return: True if the stamp actually changed.
*/
int
tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
if (vr.r.ver.nextcheck == stamp)
return 0;
vr.r.ver.nextcheck = stamp;
rc = tdbio_write_record (ctrl, &vr);
if (rc)
log_fatal (_("%s: error writing version record: %s\n"),
db_name, gpg_strerror (rc));
return 1;
}
/*
* Return the record number of the trusthash table or create one if it
* does not yet exist. On a read or write problem the process is
* terminated.
*
* Return: record number
*/
static ulong
get_trusthashrec (ctrl_t ctrl)
{
static ulong trusthashtbl; /* Record number of the trust hashtable. */
(void)ctrl;
if (!trusthashtbl)
{
TRUSTREC vr;
int rc;
rc = tdbio_read_record (0, &vr, RECTYPE_VER );
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc) );
if (!vr.r.ver.trusthashtbl)
{
/* Oops: the trustdb is corrupt because the hashtable is
* always created along with the version record. However,
* if something went initially wrong it may happen that
* there is just the version record. We try to fix it here.
* If we can't do that we return 0 - this is the version
* record and thus the actual read will detect the mismatch
* and bail out. Note that create_hashtable updates VR. */
take_write_lock ();
if (lseek (db_fd, 0, SEEK_END) == TRUST_RECORD_LEN)
create_hashtable (ctrl, &vr, 0);
release_write_lock ();
}
trusthashtbl = vr.r.ver.trusthashtbl;
}
return trusthashtbl;
}
/*
* Update a hashtable in the trustdb. TABLE gives the start of the
* table, KEY and KEYLEN are the key, NEWRECNUM is the record number
* to insert into the table.
*
* Return: 0 on success or an error code.
*/
static int
upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum)
{
TRUSTREC lastrec, rec;
ulong hashrec, item;
int msb;
int level = 0;
int rc, i;
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL);
if (rc)
{
log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc));
return rc;
}
item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item) /* Insert a new item into the hash table. */
{
rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
{
log_error ("upd_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else if (item != newrecnum) /* Must do an update. */
{
lastrec = rec;
rc = tdbio_read_record (item, &rec, 0);
if (rc)
{
log_error ("upd_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec.rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections.\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
else if (rec.rectype == RECTYPE_HLST) /* Extend the list. */
{
/* Check whether the key is already in this list. */
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec.r.hlst.rnum[i] == newrecnum)
{
return 0; /* Okay, already in the list. */
}
}
if (rec.r.hlst.next)
{
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("upd_hashtable: read hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
}
else
break; /* key is not in the list */
}
/* Find the next free entry and put it in. */
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (!rec.r.hlst.rnum[i])
{
/* Empty slot found. */
rec.r.hlst.rnum[i] = newrecnum;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_error ("upd_hashtable: write hlst failed: %s\n",
gpg_strerror (rc));
return rc; /* Done. */
}
}
if (rec.r.hlst.next)
{
/* read the next record of the list. */
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("upd_hashtable: read hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else
{
/* Append a new record to the list. */
rec.r.hlst.next = item = tdbio_new_recnum (ctrl);
rc = tdbio_write_record (ctrl, &rec);
if (rc)
{
log_error ("upd_hashtable: write hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HLST;
rec.recnum = item;
rec.r.hlst.rnum[0] = newrecnum;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_error ("upd_hashtable: write ext hlst failed: %s\n",
gpg_strerror (rc));
return rc; /* Done. */
}
} /* end loop over list slots */
}
else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record. */
{
if (rec.recnum == newrecnum)
{
return 0;
}
item = rec.recnum; /* Save number of key record. */
memset (&rec, 0, sizeof rec);
rec.rectype = RECTYPE_HLST;
rec.recnum = tdbio_new_recnum (ctrl);
rec.r.hlst.rnum[0] = item; /* Old key record */
rec.r.hlst.rnum[1] = newrecnum; /* and new key record */
rc = tdbio_write_record (ctrl, &rec);
if (rc)
{
log_error( "upd_hashtable: write new hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
/* Update the hashtable record. */
lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum;
rc = tdbio_write_record (ctrl, &lastrec);
if (rc)
log_error ("upd_hashtable: update htbl failed: %s\n",
gpg_strerror (rc));
return rc; /* Ready. */
}
else
{
log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n",
table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
if (opt.verbose > 1)
list_trustdb (ctrl, es_stderr, NULL);
return GPG_ERR_TRUSTDB;
}
}
return 0;
}
/*
* Drop an entry from a hashtable. TABLE gives the start of the
* table, KEY and KEYLEN are the key.
*
* Return: 0 on success or an error code.
*/
static int
drop_from_hashtable (ctrl_t ctrl, ulong table,
byte *key, int keylen, ulong recnum)
{
TRUSTREC rec;
ulong hashrec, item;
int msb;
int level = 0;
int rc, i;
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL );
if (rc)
{
log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc));
return rc;
}
item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item)
return 0; /* Not found - forget about it. */
if (item == recnum) /* Table points direct to the record. */
{
rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_error ("drop_from_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
rc = tdbio_read_record (item, &rec, 0);
if (rc)
{
log_error ("drop_from_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec.rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections.\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
if (rec.rectype == RECTYPE_HLST)
{
for (;;)
{
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec.r.hlst.rnum[i] == recnum)
{
rec.r.hlst.rnum[i] = 0; /* Mark as free. */
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_error("drop_from_hashtable: write htbl failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
if (rec.r.hlst.next)
{
rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
if (rc)
{
log_error ("drop_from_hashtable: read hlst failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
else
return 0; /* Key not in table. */
}
}
log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n",
table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
return GPG_ERR_TRUSTDB;
}
/*
* Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return
* the result in REC. The return value of CMP() should be True if the
* record is the desired one.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
static gpg_error_t
lookup_hashtable (ulong table, const byte *key, size_t keylen,
int (*cmpfnc)(const void*, const TRUSTREC *),
const void *cmpdata, TRUSTREC *rec )
{
int rc;
ulong hashrec, item;
int msb;
int level = 0;
if (!table)
{
rc = gpg_error (GPG_ERR_INV_RECORD);
log_error("lookup_hashtable failed: %s\n", "request for record 0");
return rc;
}
hashrec = table;
next_level:
msb = key[level];
hashrec += msb / ITEMS_PER_HTBL_RECORD;
rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL);
if (rc)
{
log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) );
return rc;
}
item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
if (!item)
return gpg_error (GPG_ERR_NOT_FOUND);
rc = tdbio_read_record (item, rec, 0);
if (rc)
{
log_error( "hashtable read failed: %s\n", gpg_strerror (rc) );
return rc;
}
if (rec->rectype == RECTYPE_HTBL)
{
hashrec = item;
level++;
if (level >= keylen)
{
log_error ("hashtable has invalid indirections\n");
return GPG_ERR_TRUSTDB;
}
goto next_level;
}
else if (rec->rectype == RECTYPE_HLST)
{
for (;;)
{
int i;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
if (rec->r.hlst.rnum[i])
{
TRUSTREC tmp;
rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0);
if (rc)
{
log_error ("lookup_hashtable: read item failed: %s\n",
gpg_strerror (rc));
return rc;
}
if ((*cmpfnc)(cmpdata, &tmp))
{
*rec = tmp;
return 0;
}
}
}
if (rec->r.hlst.next)
{
rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST);
if (rc)
{
log_error ("lookup_hashtable: read hlst failed: %s\n",
gpg_strerror (rc) );
return rc;
}
}
else
return gpg_error (GPG_ERR_NOT_FOUND);
}
}
if ((*cmpfnc)(cmpdata, rec))
return 0; /* really found */
return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */
}
/*
* Update the trust hash table TR or create the table if it does not
* exist.
*
* Return: 0 on success or an error code.
*/
static int
update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr)
{
return upd_hashtable (ctrl, get_trusthashrec (ctrl),
tr->r.trust.fingerprint, 20, tr->recnum);
}
/*
* Dump the trustdb record REC to stream FP.
*/
void
tdbio_dump_record (TRUSTREC *rec, estream_t fp)
{
int i;
ulong rnum = rec->recnum;
es_fprintf (fp, "rec %5lu, ", rnum);
switch (rec->rectype)
{
case 0:
es_fprintf (fp, "blank\n");
break;
case RECTYPE_VER:
es_fprintf (fp,
"version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n",
rec->r.ver.trusthashtbl,
rec->r.ver.firstfree,
rec->r.ver.marginals,
rec->r.ver.completes,
rec->r.ver.cert_depth,
rec->r.ver.trust_model,
rec->r.ver.min_cert_level,
rec->r.ver.nextcheck,
strtimestamp(rec->r.ver.nextcheck)
);
break;
case RECTYPE_FREE:
es_fprintf (fp, "free, next=%lu\n", rec->r.free.next);
break;
case RECTYPE_HTBL:
es_fprintf (fp, "htbl,");
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
es_fprintf (fp, " %lu", rec->r.htbl.item[i]);
es_putc ('\n', fp);
break;
case RECTYPE_HLST:
es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next);
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]);
es_putc ('\n', fp);
break;
case RECTYPE_TRUST:
es_fprintf (fp, "trust ");
for (i=0; i < 20; i++)
es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]);
es_fprintf (fp, ", ot=%d, d=%d, vl=%lu\n", rec->r.trust.ownertrust,
rec->r.trust.depth, rec->r.trust.validlist);
break;
case RECTYPE_VALID:
es_fprintf (fp, "valid ");
for (i=0; i < 20; i++)
es_fprintf(fp, "%02X", rec->r.valid.namehash[i]);
es_fprintf (fp, ", v=%d, next=%lu\n", rec->r.valid.validity,
rec->r.valid.next);
break;
default:
es_fprintf (fp, "unknown type %d\n", rec->rectype );
break;
}
}
/*
* Read the record with number RECNUM into the structure REC. If
* EXPECTED is not 0 reading any other record type will return an
* error.
*
* Return: 0 on success or an error code.
*/
int
tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected)
{
byte readbuf[TRUST_RECORD_LEN];
const byte *buf, *p;
gpg_error_t err = 0;
int n, i;
if (db_fd == -1)
open_db ();
buf = get_record_from_cache( recnum );
if (!buf)
{
if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb: lseek failed: %s\n"), strerror (errno));
return err;
}
n = read (db_fd, readbuf, TRUST_RECORD_LEN);
if (!n)
{
return gpg_error (GPG_ERR_EOF);
}
else if (n != TRUST_RECORD_LEN)
{
err = gpg_error_from_syserror ();
log_error (_("trustdb: read failed (n=%d): %s\n"),
n, strerror(errno));
return err;
}
buf = readbuf;
}
rec->recnum = recnum;
rec->dirty = 0;
p = buf;
rec->rectype = *p++;
if (expected && rec->rectype != expected)
{
log_error ("%lu: read expected rec type %d, got %d\n",
recnum, expected, rec->rectype);
return gpg_error (GPG_ERR_TRUSTDB);
}
p++; /* Skip reserved byte. */
switch (rec->rectype)
{
case 0: /* unused (free) record */
break;
case RECTYPE_VER: /* version record */
if (memcmp(buf+1, GPGEXT_GPG, 3))
{
log_error (_("%s: not a trustdb file\n"), db_name );
err = gpg_error (GPG_ERR_TRUSTDB);
}
else
{
p += 2; /* skip "gpg" */
rec->r.ver.version = *p++;
rec->r.ver.marginals = *p++;
rec->r.ver.completes = *p++;
rec->r.ver.cert_depth = *p++;
rec->r.ver.trust_model = *p++;
rec->r.ver.min_cert_level = *p++;
p += 2;
rec->r.ver.created = buf32_to_ulong(p);
p += 4;
rec->r.ver.nextcheck = buf32_to_ulong(p);
p += 4;
p += 4;
p += 4;
rec->r.ver.firstfree = buf32_to_ulong(p);
p += 4;
p += 4;
rec->r.ver.trusthashtbl = buf32_to_ulong(p);
if (recnum)
{
log_error( _("%s: version record with recnum %lu\n"), db_name,
(ulong)recnum );
err = gpg_error (GPG_ERR_TRUSTDB);
}
else if (rec->r.ver.version != 3)
{
log_error( _("%s: invalid file version %d\n"), db_name,
rec->r.ver.version );
err = gpg_error (GPG_ERR_TRUSTDB);
}
}
break;
case RECTYPE_FREE:
rec->r.free.next = buf32_to_ulong(p);
break;
case RECTYPE_HTBL:
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
{
rec->r.htbl.item[i] = buf32_to_ulong(p);
p += 4;
}
break;
case RECTYPE_HLST:
rec->r.hlst.next = buf32_to_ulong(p);
p += 4;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
{
rec->r.hlst.rnum[i] = buf32_to_ulong(p);
p += 4;
}
break;
case RECTYPE_TRUST:
memcpy (rec->r.trust.fingerprint, p, 20);
p+=20;
rec->r.trust.ownertrust = *p++;
rec->r.trust.depth = *p++;
rec->r.trust.min_ownertrust = *p++;
p++;
rec->r.trust.validlist = buf32_to_ulong(p);
break;
case RECTYPE_VALID:
memcpy (rec->r.valid.namehash, p, 20);
p+=20;
rec->r.valid.validity = *p++;
rec->r.valid.next = buf32_to_ulong(p);
p += 4;
rec->r.valid.full_count = *p++;
rec->r.valid.marginal_count = *p++;
break;
default:
log_error ("%s: invalid record type %d at recnum %lu\n",
db_name, rec->rectype, (ulong)recnum);
err = gpg_error (GPG_ERR_TRUSTDB);
break;
}
return err;
}
/*
* Write the record from the struct REC.
*
* Return: 0 on success or an error code.
*/
int
tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec)
{
byte buf[TRUST_RECORD_LEN];
byte *p;
int rc = 0;
int i;
ulong recnum = rec->recnum;
if (db_fd == -1)
open_db ();
memset (buf, 0, TRUST_RECORD_LEN);
p = buf;
*p++ = rec->rectype; p++;
switch (rec->rectype)
{
case 0: /* unused record */
break;
case RECTYPE_VER: /* version record */
if (recnum)
BUG ();
memcpy(p-1, GPGEXT_GPG, 3 ); p += 2;
*p++ = rec->r.ver.version;
*p++ = rec->r.ver.marginals;
*p++ = rec->r.ver.completes;
*p++ = rec->r.ver.cert_depth;
*p++ = rec->r.ver.trust_model;
*p++ = rec->r.ver.min_cert_level;
p += 2;
ulongtobuf(p, rec->r.ver.created); p += 4;
ulongtobuf(p, rec->r.ver.nextcheck); p += 4;
p += 4;
p += 4;
ulongtobuf(p, rec->r.ver.firstfree ); p += 4;
p += 4;
ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4;
break;
case RECTYPE_FREE:
ulongtobuf(p, rec->r.free.next); p += 4;
break;
case RECTYPE_HTBL:
for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
{
ulongtobuf( p, rec->r.htbl.item[i]); p += 4;
}
break;
case RECTYPE_HLST:
ulongtobuf( p, rec->r.hlst.next); p += 4;
for (i=0; i < ITEMS_PER_HLST_RECORD; i++ )
{
ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4;
}
break;
case RECTYPE_TRUST:
memcpy (p, rec->r.trust.fingerprint, 20); p += 20;
*p++ = rec->r.trust.ownertrust;
*p++ = rec->r.trust.depth;
*p++ = rec->r.trust.min_ownertrust;
p++;
ulongtobuf( p, rec->r.trust.validlist); p += 4;
break;
case RECTYPE_VALID:
memcpy (p, rec->r.valid.namehash, 20); p += 20;
*p++ = rec->r.valid.validity;
ulongtobuf( p, rec->r.valid.next); p += 4;
*p++ = rec->r.valid.full_count;
*p++ = rec->r.valid.marginal_count;
break;
default:
BUG();
}
rc = put_record_into_cache (recnum, buf);
if (rc)
;
else if (rec->rectype == RECTYPE_TRUST)
rc = update_trusthashtbl (ctrl, rec);
return rc;
}
/*
* Delete the record at record number RECNUm from the trustdb.
*
* Return: 0 on success or an error code.
*/
int
tdbio_delete_record (ctrl_t ctrl, ulong recnum)
{
TRUSTREC vr, rec;
int rc;
/* Must read the record fist, so we can drop it from the hash tables */
rc = tdbio_read_record (recnum, &rec, 0);
if (rc)
;
else if (rec.rectype == RECTYPE_TRUST)
{
rc = drop_from_hashtable (ctrl, get_trusthashrec (ctrl),
rec.r.trust.fingerprint, 20, rec.recnum);
}
if (rc)
return rc;
/* Now we can change it to a free record. */
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal (_("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
rec.recnum = recnum;
rec.rectype = RECTYPE_FREE;
rec.r.free.next = vr.r.ver.firstfree;
vr.r.ver.firstfree = recnum;
rc = tdbio_write_record (ctrl, &rec);
if (!rc)
rc = tdbio_write_record (ctrl, &vr);
return rc;
}
/*
* Create a new record and return its record number.
*/
ulong
tdbio_new_recnum (ctrl_t ctrl)
{
off_t offset;
ulong recnum;
TRUSTREC vr, rec;
int rc;
/* Look for unused records. */
rc = tdbio_read_record (0, &vr, RECTYPE_VER);
if (rc)
log_fatal( _("%s: error reading version record: %s\n"),
db_name, gpg_strerror (rc));
if (vr.r.ver.firstfree)
{
recnum = vr.r.ver.firstfree;
rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE);
if (rc)
log_fatal (_("%s: error reading free record: %s\n"),
db_name, gpg_strerror (rc));
/* Update dir record. */
vr.r.ver.firstfree = rec.r.free.next;
rc = tdbio_write_record (ctrl, &vr);
if (rc)
log_fatal (_("%s: error writing dir record: %s\n"),
db_name, gpg_strerror (rc));
/* Zero out the new record. */
memset (&rec, 0, sizeof rec);
rec.rectype = 0; /* Mark as unused record (actually already done
my the memset). */
rec.recnum = recnum;
rc = tdbio_write_record (ctrl, &rec);
if (rc)
log_fatal (_("%s: failed to zero a record: %s\n"),
db_name, gpg_strerror (rc));
}
else /* Not found - append a new record. */
{
offset = lseek (db_fd, 0, SEEK_END);
if (offset == (off_t)(-1))
log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno));
recnum = offset / TRUST_RECORD_LEN;
log_assert (recnum); /* This will never be the first record */
/* We must write a record, so that the next call to this
* function returns another recnum. */
memset (&rec, 0, sizeof rec);
rec.rectype = 0; /* unused record */
rec.recnum = recnum;
rc = 0;
if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
{
rc = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: lseek failed: %s\n"),
recnum, strerror (errno));
}
else
{
int n;
n = write (db_fd, &rec, TRUST_RECORD_LEN);
if (n != TRUST_RECORD_LEN)
{
rc = gpg_error_from_syserror ();
log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
recnum, n, gpg_strerror (rc));
}
}
if (rc)
log_fatal (_("%s: failed to append a record: %s\n"),
db_name, gpg_strerror (rc));
}
return recnum ;
}
/* Helper function for tdbio_search_trust_byfpr. */
static int
cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec )
{
return (rec->rectype == RECTYPE_TRUST
&& !memcmp (rec->r.trust.fingerprint, fpr, 20));
}
/*
* Given a 20 byte FINGERPRINT search its trust record and return
* that at REC.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
gpg_error_t
tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, TRUSTREC *rec)
{
int rc;
/* Locate the trust record using the hash table */
rc = lookup_hashtable (get_trusthashrec (ctrl), fingerprint, 20,
cmp_trec_fpr, fingerprint, rec );
return rc;
}
/*
* Given a primary public key object PK search its trust record and
* return that at REC.
*
* Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
*/
gpg_error_t
tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec)
{
byte fingerprint[MAX_FINGERPRINT_LEN];
size_t fingerlen;
fingerprint_from_pk( pk, fingerprint, &fingerlen );
for (; fingerlen < 20; fingerlen++)
fingerprint[fingerlen] = 0;
return tdbio_search_trust_byfpr (ctrl, fingerprint, rec);
}
/*
* Terminate the process with a message about a corrupted trustdb.
*/
void
tdbio_invalid (void)
{
log_error (_("Error: The trustdb is corrupted.\n"));
how_to_fix_the_trustdb ();
g10_exit (2);
}
diff --git a/g13/g13tuple.c b/g13/g13tuple.c
index 6693826ad..746c7900d 100644
--- a/g13/g13tuple.c
+++ b/g13/g13tuple.c
@@ -1,340 +1,340 @@
/* g13tuple.c - Tuple handling
* Copyright (C) 2009 Free Software Foundation, Inc.
* Copyright (C) 2009, 2015, 2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include "g13.h"
#include "g13tuple.h"
#include "keyblob.h" /* Required for dump_tupledesc. */
/* Definition of the tuple descriptor object. */
struct tupledesc_s
{
unsigned char *data; /* The tuple data. */
size_t datalen; /* The length of the data. */
size_t pos; /* The current position as used by next_tuple. */
int refcount; /* Number of references hold. */
};
/* Append the TAG and the VALUE to the MEMBUF. There is no error
checking here; this is instead done while getting the value back
from the membuf. */
void
append_tuple (membuf_t *membuf, int tag, const void *value, size_t length)
{
unsigned char buf[2];
assert (tag >= 0 && tag <= 0xffff);
assert (length <= 0xffff);
buf[0] = tag >> 8;
buf[1] = tag;
put_membuf (membuf, buf, 2);
buf[0] = length >> 8;
buf[1] = length;
put_membuf (membuf, buf, 2);
if (length)
put_membuf (membuf, value, length);
}
/* Append the unsigned integer VALUE under TAG to MEMBUF. We make
* sure that the most significant bit is always cleared to explicitly
* flag the value as unsigned. */
void
append_tuple_uint (membuf_t *membuf, int tag, unsigned long long value)
{
unsigned char buf[16];
unsigned char *p;
unsigned int len;
p = buf + sizeof buf;
len = 0;
do
{
if (p == buf)
BUG () ;
*--p = (value & 0xff);
value >>= 8;
len++;
}
while (value);
/* Prepend a zero byte if the first byte has its MSB set. */
if ((*p & 0x80))
{
if (p == buf)
BUG () ;
*--p = 0;
len++;
}
append_tuple (membuf, tag, p, len);
}
/* Create a tuple object by moving the ownership of (DATA,DATALEN) to
* a new object. Returns 0 on success and stores the new object at
* R_TUPLEHD. The return object must be released using
* destroy_tuples(). */
gpg_error_t
create_tupledesc (tupledesc_t *r_desc, void *data, size_t datalen)
{
if (datalen < 5 || memcmp (data, "\x00\x00\x00\x01\x01", 5))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
*r_desc = xtrymalloc (sizeof **r_desc);
if (!*r_desc)
return gpg_error_from_syserror ();
(*r_desc)->data = data;
(*r_desc)->datalen = datalen;
(*r_desc)->pos = 0;
(*r_desc)->refcount = 1;
return 0;
}
/* Unref a tuple descriptor and if the refcount is down to 0 release
its allocated storage. */
void
destroy_tupledesc (tupledesc_t tupledesc)
{
if (!tupledesc)
return;
if (!--tupledesc->refcount)
{
xfree (tupledesc->data);
xfree (tupledesc);
}
}
tupledesc_t
ref_tupledesc (tupledesc_t tupledesc)
{
if (tupledesc)
tupledesc->refcount++;
return tupledesc;
}
/* Return a pointer to the memory used to store the tuples. This is
- * the data originally provided to create_tupledesc. It is higly
+ * the data originally provided to create_tupledesc. It is highly
* recommended that the callers uses ref_tupledesc before calling this
* function and unref_tupledesc when the return data will not anymore
* be used. */
const void *
get_tupledesc_data (tupledesc_t tupledesc, size_t *r_datalen)
{
*r_datalen = tupledesc->datalen;
return tupledesc->data;
}
/* Find the first tuple with tag TAG. On success return a pointer to
its value and store the length of the value at R_LENGTH. If no
tuple was found return NULL. For use by next_tuple, the last
position is stored in the descriptor. */
const void *
find_tuple (tupledesc_t tupledesc, unsigned int tag, size_t *r_length)
{
const unsigned char *s;
const unsigned char *s_end; /* Points right behind the data. */
unsigned int t;
size_t n;
s = tupledesc->data;
if (!s)
return NULL;
s_end = s + tupledesc->datalen;
while (s < s_end)
{
/* We use addresses for the overflow check to avoid undefined
behaviour. size_t should work with all flat memory models. */
if ((size_t)s+3 >= (size_t)s_end || (size_t)s + 3 < (size_t)s)
break;
t = s[0] << 8;
t |= s[1];
n = s[2] << 8;
n |= s[3];
s += 4;
if ((size_t)s + n > (size_t)s_end || (size_t)s + n < (size_t)s)
break;
if (t == tag)
{
tupledesc->pos = (s + n) - tupledesc->data;
*r_length = n;
return s;
}
s += n;
}
return NULL;
}
/* Helper for find_tuple_uint and others. */
static gpg_error_t
convert_uint (const unsigned char *s, size_t n, unsigned long long *r_value)
{
unsigned long long value = 0;
*r_value = 0;
if (!s)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!n || (*s & 0x80)) /* No bytes or negative. */
return gpg_error (GPG_ERR_ERANGE);
if (n && !*s) /* Skip a leading zero. */
{
n--;
s++;
}
if (n > sizeof value)
return gpg_error (GPG_ERR_ERANGE);
for (; n; n--, s++)
{
value <<= 8;
value |= *s;
}
*r_value = value;
return 0;
}
/* Similar to find-tuple but expects an unsigned int value and stores
* that at R_VALUE. If the tag was not found GPG_ERR_NOT_FOUND is
* returned and 0 stored at R_VALUE. If the value cannot be converted
* to an unsigned integer GPG_ERR_ERANGE is returned. */
gpg_error_t
find_tuple_uint (tupledesc_t tupledesc, unsigned int tag,
unsigned long long *r_value)
{
const unsigned char *s;
size_t n;
s = find_tuple (tupledesc, tag, &n);
return convert_uint (s, n, r_value);
}
const void *
next_tuple (tupledesc_t tupledesc, unsigned int *r_tag, size_t *r_length)
{
const unsigned char *s;
const unsigned char *s_end; /* Points right behind the data. */
unsigned int t;
size_t n;
s = tupledesc->data;
if (!s)
return NULL;
s_end = s + tupledesc->datalen;
s += tupledesc->pos;
if (s < s_end
&& !((size_t)s + 3 >= (size_t)s_end || (size_t)s + 3 < (size_t)s))
{
t = s[0] << 8;
t |= s[1];
n = s[2] << 8;
n |= s[3];
s += 4;
if (!((size_t)s + n > (size_t)s_end || (size_t)s + n < (size_t)s))
{
tupledesc->pos = (s + n) - tupledesc->data;
*r_tag = t;
*r_length = n;
return s;
}
}
return NULL;
}
/* Return true if BUF has only printable characters. */
static int
all_printable (const void *buf, size_t buflen)
{
const unsigned char *s;
for (s=buf ; buflen; s++, buflen--)
if (*s < 32 || *s > 126)
return 0;
return 1;
}
/* Print information about TUPLES to the log stream. */
void
dump_tupledesc (tupledesc_t tuples)
{
size_t n;
unsigned int tag;
const void *value;
unsigned long long uint;
log_info ("keyblob dump:\n");
tag = KEYBLOB_TAG_BLOBVERSION;
value = find_tuple (tuples, tag, &n);
while (value)
{
log_info (" tag: %-5u len: %-2u value: ", tag, (unsigned int)n);
if (!n)
log_printf ("[none]\n");
else
{
switch (tag)
{
case KEYBLOB_TAG_ENCKEY:
case KEYBLOB_TAG_MACKEY:
log_printf ("[confidential]\n");
break;
case KEYBLOB_TAG_ALGOSTR:
if (n < 100 && all_printable (value, n))
log_printf ("%.*s\n", (int)n, (const char*)value);
else
log_printhex (value, n, "");
break;
case KEYBLOB_TAG_CONT_NSEC:
case KEYBLOB_TAG_ENC_NSEC:
case KEYBLOB_TAG_ENC_OFF:
if (!convert_uint (value, n, &uint))
log_printf ("%llu\n", uint);
else
log_printhex (value, n, "");
break;
default:
log_printhex (value, n, "");
break;
}
}
value = next_tuple (tuples, &tag, &n);
}
}
diff --git a/g13/server.c b/g13/server.c
index 780295214..341a6b281 100644
--- a/g13/server.c
+++ b/g13/server.c
@@ -1,783 +1,783 @@
/* server.c - The G13 Assuan server
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "g13.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "keyblob.h"
#include "server.h"
#include "create.h"
#include "mount.h"
#include "suspend.h"
#include "../common/server-help.h"
#include "../common/asshelp.h"
#include "../common/call-gpg.h"
/* The filepointer for status message used in non-server mode */
static FILE *statusfp;
/* Local data for this server module. A pointer to this is stored in
the CTRL object of each connection. */
struct server_local_s
{
/* The Assuan context we are working on. */
assuan_context_t assuan_ctx;
char *containername; /* Malloced active containername. */
};
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/*
Helper functions.
*/
/* Set an error and a description. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* The handler for Assuan OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
(void)ctrl;
if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (opt.session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (opt.session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
err = session_env_setenv (opt.session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
err = session_env_setenv (opt.session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
xfree (opt.lc_ctype);
opt.lc_ctype = xtrystrdup (value);
if (!opt.lc_ctype)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "lc-messages"))
{
xfree (opt.lc_messages);
opt.lc_messages = xtrystrdup (value);
if (!opt.lc_messages)
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (opt.session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (opt.session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "allow-pinentry-notify"))
{
; /* We always allow it. */
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* The handler for an Assuan RESET command. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
FREE_STRLIST (ctrl->recipients);
assuan_close_input_fd (ctx);
assuan_close_output_fd (ctx);
return 0;
}
static const char hlp_open[] =
"OPEN [<options>] <filename>\n"
"\n"
"Open the container FILENAME. FILENAME must be percent-plus\n"
"escaped. A quick check to see whether this is a suitable G13\n"
"container file is done. However no cryptographic check or any\n"
"other check is done. This command is used to define the target for\n"
"further commands. The filename is reset with the RESET command,\n"
"another OPEN or the CREATE command.";
static gpg_error_t
cmd_open (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
/* In any case reset the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Do a basic check. */
err = g13_is_container (ctrl, line);
if (err)
goto leave;
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
leave:
return leave_cmd (ctx, err);
}
static const char hlp_mount[] =
"MOUNT [options] [<mountpoint>]\n"
"\n"
"Mount the currently open file onto MOUNTPOINT. If MOUNTPOINT is not\n"
"given the system picks an unused mountpoint. MOUNTPOINT must\n"
"be percent-plus escaped to allow for arbitrary names.";
static gpg_error_t
cmd_mount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
if (!ctrl->server_local->containername)
{
err = gpg_error (GPG_ERR_MISSING_ACTION);
goto leave;
}
/* Perform the mount. */
err = g13_mount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_umount[] =
"UMOUNT [options] [<mountpoint>]\n"
"\n"
"Unmount the currently open file or the one opened at MOUNTPOINT.\n"
"MOUNTPOINT must be percent-plus escaped. On success the mountpoint\n"
"is returned via a \"MOUNTPOINT\" status line.";
static gpg_error_t
cmd_umount (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
char *p, *pend;
size_t len;
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Perform the unmount. */
err = g13_umount_container (ctrl, ctrl->server_local->containername,
*line? line : NULL);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_suspend[] =
"SUSPEND\n"
"\n"
"Suspend the currently set device.";
static gpg_error_t
cmd_suspend (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_suspend_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_resume[] =
"RESUME\n"
"\n"
"Resume the currently set device.";
static gpg_error_t
cmd_resume (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
line = skip_options (line);
if (*line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
/* Perform the suspend operation. */
err = g13_resume_container (ctrl, ctrl->server_local->containername);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_recipient[] =
"RECIPIENT <userID>\n"
"\n"
"Add USERID to the list of recipients to be used for the next CREATE\n"
"command. All recipient commands are cumulative until a RESET or an\n"
"successful create command.";
static gpg_error_t
cmd_recipient (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
line = skip_options (line);
if (!add_to_strlist_try (&ctrl->recipients, line))
err = gpg_error_from_syserror ();
return leave_cmd (ctx, err);
}
static const char hlp_signer[] =
"SIGNER <userID>\n"
"\n"
"Not yet implemented.";
static gpg_error_t
cmd_signer (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
(void)ctrl;
(void)line;
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return leave_cmd (ctx, err);
}
static const char hlp_create[] =
"CREATE [options] <filename>\n"
"\n"
"Create a new container. On success the OPEN command is \n"
- "implictly done for the new container.";
+ "implicitly done for the new container.";
static gpg_error_t
cmd_create (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *pend;
size_t len;
/* First we close the active container. */
xfree (ctrl->server_local->containername);
ctrl->server_local->containername = NULL;
/* Parse the line. */
line = skip_options (line);
for (p=line; *p && !spacep (p); p++)
;
pend = p;
while (spacep(p))
p++;
if (*p || pend == line)
{
err = gpg_error (GPG_ERR_ASS_SYNTAX);
goto leave;
}
*pend = 0;
/* Unescape the line and check for embedded Nul bytes. */
len = percent_plus_unescape_inplace (line, 0);
line[len] = 0;
if (!len || memchr (line, 0, len))
{
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
/* Create container. */
err = g13_create_container (ctrl, line);
if (!err)
{
FREE_STRLIST (ctrl->recipients);
/* Store the filename. */
ctrl->server_local->containername = xtrystrdup (line);
if (!ctrl->server_local->containername)
err = gpg_error_from_syserror ();
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" cmd_has_option CMD OPT\n"
" - Return OK if the command CMD implements the option OPT.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
if (!strcmp (line, "version"))
{
const char *s = PACKAGE_VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
err = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
/* Return true if the command CMD implements the option CMDOPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
(void)cmd;
(void)cmdopt;
return 0;
}
/* Tell the Assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "OPEN", cmd_open, hlp_open },
{ "MOUNT", cmd_mount, hlp_mount},
{ "UMOUNT", cmd_umount, hlp_umount },
{ "SUSPEND", cmd_suspend, hlp_suspend },
{ "RESUME", cmd_resume, hlp_resume },
{ "RECIPIENT", cmd_recipient, hlp_recipient },
{ "SIGNER", cmd_signer, hlp_signer },
{ "CREATE", cmd_create, hlp_create },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETINFO", cmd_getinfo,hlp_getinfo },
{ NULL }
};
gpg_error_t err;
int i;
for (i=0; table[i].name; i++)
{
err = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (err)
return err;
}
return 0;
}
/* Startup the server. DEFAULT_RECPLIST is the list of recipients as
set from the command line or config file. We only require those
marked as encrypt-to. */
gpg_error_t
g13_server (ctrl_t ctrl)
{
gpg_error_t err;
assuan_fd_t filedes[2];
assuan_context_t ctx = NULL;
static const char hello[] = ("GNU Privacy Guard's G13 server "
PACKAGE_VERSION " ready");
/* We use a pipe based server so that we can work from scripts.
assuan_init_pipe_server will automagically detect when we are
called with a socketpair and ignore FIELDES in this case. */
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
err = assuan_new (&ctx);
if (err)
{
log_error ("failed to allocate an Assuan context: %s\n",
gpg_strerror (err));
goto leave;
}
err = assuan_init_pipe_server (ctx, filedes);
if (err)
{
log_error ("failed to initialize the server: %s\n", gpg_strerror (err));
goto leave;
}
err = register_commands (ctx);
if (err)
{
log_error ("failed to the register commands with Assuan: %s\n",
gpg_strerror (err));
goto leave;
}
assuan_set_pointer (ctx, ctrl);
if (opt.verbose || opt.debug)
{
char *tmp;
tmp = xtryasprintf ("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename,
hello);
if (tmp)
{
assuan_set_hello_line (ctx, tmp);
xfree (tmp);
}
}
else
assuan_set_hello_line (ctx, hello);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->assuan_ctx = ctx;
while ( !(err = assuan_accept (ctx)) )
{
err = assuan_process (ctx);
if (err)
log_info ("Assuan processing failed: %s\n", gpg_strerror (err));
}
if (err == -1)
err = 0;
else
log_info ("Assuan accept problem: %s\n", gpg_strerror (err));
leave:
reset_notify (ctx, NULL); /* Release all items hold by SERVER_LOCAL. */
if (ctrl->server_local)
{
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
assuan_release (ctx);
return err;
}
/* Send a status line with status ID NO. The arguments are a list of
strings terminated by a NULL argument. */
gpg_error_t
g13_status (ctrl_t ctrl, int no, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
const char *text;
va_start (arg_ptr, no);
if (ctrl->no_server && ctrl->status_fd == -1)
; /* No status wanted. */
else if (ctrl->no_server)
{
if (!statusfp)
{
if (ctrl->status_fd == 1)
statusfp = stdout;
else if (ctrl->status_fd == 2)
statusfp = stderr;
else
statusfp = fdopen (ctrl->status_fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
ctrl->status_fd, strerror(errno));
}
}
fputs ("[GNUPG:] ", statusfp);
fputs (get_status_string (no), statusfp);
while ( (text = va_arg (arg_ptr, const char*) ))
{
putc ( ' ', statusfp );
for (; *text; text++)
{
if (*text == '\n')
fputs ( "\\n", statusfp );
else if (*text == '\r')
fputs ( "\\r", statusfp );
else
putc ( *(const byte *)text, statusfp );
}
}
putc ('\n', statusfp);
fflush (statusfp);
}
else
{
err = vprint_assuan_status_strings (ctrl->server_local->assuan_ctx,
get_status_string (no), arg_ptr);
}
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about Pinentry events. Returns an gpg
error code. */
gpg_error_t
g13_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line)
{
if (!ctrl || !ctrl->server_local)
return 0;
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/*
* Decrypt the keyblob (ENCKEYBLOB,ENCKEYBLOBLEN) and store the result
* at (R_KEYBLOB, R_KEYBLOBLEN). Returns 0 on success or an error
* code. On error R_KEYBLOB is set to NULL.
*
* This actually does not belong here but for that simple wrapper it
* does not make sense to add another source file. Note that we do
* not want to have this in keyblob.c, because that code is also used
* by the syshelp.
*/
gpg_error_t
g13_keyblob_decrypt (ctrl_t ctrl, const void *enckeyblob, size_t enckeybloblen,
void **r_keyblob, size_t *r_keybloblen)
{
gpg_error_t err;
/* FIXME: For now we only implement OpenPGP. */
err = gpg_decrypt_blob (ctrl, opt.gpg_program, opt.gpg_arguments,
enckeyblob, enckeybloblen,
r_keyblob, r_keybloblen);
return err;
}
diff --git a/kbx/backend-cache.c b/kbx/backend-cache.c
index 45e5c7158..eaef01cf4 100644
--- a/kbx/backend-cache.c
+++ b/kbx/backend-cache.c
@@ -1,1200 +1,1200 @@
/* backend-cache.c - Cache backend for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/*
* This cache backend is designed to be queried first and to deliver
* cached items (which may also be not-found). A set a maintenance
* functions is used used by the frontend to fill the cache.
* FIXME: Support x.509
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/host2net.h"
#include "backend.h"
#include "keybox-defs.h"
/* Standard values for the number of buckets and the threshold we use
* to flush items. */
#define NO_OF_KEY_ITEM_BUCKETS 383
#define KEY_ITEMS_PER_BUCKET_THRESHOLD 40
#define NO_OF_BLOB_BUCKETS 383
#define BLOBS_PER_BUCKET_THRESHOLD 20
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_CACHE. */
unsigned int backend_id; /* Always the id the backend. */
};
/* The object holding a blob. */
typedef struct blob_s
{
struct blob_s *next;
enum pubkey_types pktype;
unsigned int refcount;
unsigned int usecount;
unsigned int datalen;
unsigned char *data; /* The actual data of length DATALEN. */
unsigned char ubid[UBID_LEN];
} *blob_t;
static blob_t *blob_table; /* Hash table with the blobs. */
static size_t blob_table_size; /* Number of allocated buckets. */
static unsigned int blob_table_threshold; /* Max. # of items per bucket. */
static unsigned int blob_table_added; /* Number of items added. */
static unsigned int blob_table_dropped; /* Number of items dropped. */
static blob_t blob_attic; /* List of freed blobs. */
/* A list item to blob data. This is so that a next operation on a
* cached key item can actually work. Things are complicated because
* we do not want to force caching all object before we get a next
* request from the client. To accomplish this we keep a flag
* indicating that the search needs to continue instead of delivering
* the previous item from the cache. */
typedef struct bloblist_s
{
struct bloblist_s *next;
unsigned int final_kid:1; /* The final blob for KID searches. */
unsigned int final_fpr:1; /* The final blob for FPR searches. */
unsigned int ubid_valid:1; /* The blobid below is valid. */
unsigned int subkey:1; /* The entry is for a subkey. */
unsigned int fprlen:8; /* The length of the fingerprint or 0. */
char fpr[32]; /* The buffer for the fingerprint. */
unsigned char ubid[UBID_LEN]; /* The Unique-Blob-ID of the blob. */
} *bloblist_t;
static bloblist_t bloblist_attic; /* List of freed items. */
/* The cache object. For indexing we could use the fingerprint
* directly as a hash value. However, we use the keyid instead
* because the keyid is used by OpenPGP in encrypted packets and older
* signatures to identify a key. Since v4 OpenPGP keys the keyid is
* anyway a part of the fingerprint so it quickly extracted from a
* fingerprint. Note that v3 keys are not supported by gpg.
* FIXME: Add support for X.509.
*/
typedef struct key_item_s
{
struct key_item_s *next;
bloblist_t blist; /* List of blobs or NULL for not-found. */
unsigned int usecount;
unsigned int refcount; /* Reference counter for this item. */
u32 kid_h; /* Upper 4 bytes of the keyid. */
u32 kid_l; /* Lower 4 bytes of the keyid. */
} *key_item_t;
static key_item_t *key_table; /* Hash table with the keys. */
static size_t key_table_size; /* Number of allocated buckets. */
static unsigned int key_table_threshold; /* Max. # of items per bucket. */
static unsigned int key_table_added; /* Number of items added. */
static unsigned int key_table_dropped; /* Number of items dropped. */
static key_item_t key_item_attic; /* List of freed items. */
/* The hash function we use for the key_table. Must not call a system
* function. */
static inline unsigned int
blob_table_hasher (const unsigned char *ubid)
{
return (ubid[0] << 16 | ubid[1]) % blob_table_size;
}
/* Runtime allocation of the key table. This allows us to eventually
* add an option to control the size. */
static gpg_error_t
blob_table_init (void)
{
if (blob_table)
return 0;
blob_table_size = NO_OF_BLOB_BUCKETS;
blob_table_threshold = BLOBS_PER_BUCKET_THRESHOLD;
blob_table = xtrycalloc (blob_table_size, sizeof *blob_table);
if (!blob_table)
return gpg_error_from_syserror ();
return 0;
}
/* Free a blob. This is done by moving it to the attic list. */
static void
blob_unref (blob_t blob)
{
void *p;
if (!blob)
return;
log_assert (blob->refcount);
if (!--blob->refcount)
{
p = blob->data;
blob->data = NULL;
blob->next = blob_attic;
blob_attic = blob;
xfree (p);
}
}
/* Given the hash value and the ubid, find the blob in the bucket.
* Returns NULL if not found or the blob item if found. Always
* returns the the number of items searched, which is in the case of a
* not-found the length of the chain. */
static blob_t
find_blob (unsigned int hash, const unsigned char *ubid,
unsigned int *r_count)
{
blob_t b;
unsigned int count = 0;
for (b = blob_table[hash]; b; b = b->next, count++)
if (!memcmp (b->ubid, ubid, UBID_LEN))
break;
if (r_count)
*r_count = count;
return b;
}
/* Helper for the qsort in key_table_put. */
static int
compare_blobs (const void *arg_a, const void *arg_b)
{
const blob_t a = *(const blob_t *)arg_a;
const blob_t b = *(const blob_t *)arg_b;
/* Reverse sort on the usecount. */
if (a->usecount > b->usecount)
return -1;
else if (a->usecount == b->usecount)
return 0;
else
return 1;
}
/* Put the blob (BLOBDATA, BLOBDATALEN) into the cache using UBID as
* the index. If it is already in the cache nothing happens. */
static void
blob_table_put (const unsigned char *ubid, enum pubkey_types pktype,
const void *blobdata, unsigned int blobdatalen)
{
unsigned int hash;
blob_t b;
unsigned int count, n;
void *blobdatacopy = NULL;
hash = blob_table_hasher (ubid);
find_again:
b = find_blob (hash, ubid, &count);
if (b)
{
xfree (blobdatacopy);
return; /* Already got this blob. */
}
/* Create a copy of the blob if not yet done. */
if (!blobdatacopy)
{
blobdatacopy = xtrymalloc (blobdatalen);
if (!blobdatacopy)
{
log_info ("Note: malloc failed while copying blob to the cache: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return; /* Out of core - ignore. */
}
memcpy (blobdatacopy, blobdata, blobdatalen);
}
/* If the bucket is full remove a couple of items. */
if (count >= blob_table_threshold)
{
blob_t list_head, *list_tailp, b_next;
blob_t *array;
int narray, idx;
/* Unlink from the global list so that other threads don't
* disturb us. If another thread adds or removes something only
* one will be the winner. Bad luck for the dropped cache items
* but after all it is just a cache. */
list_head = blob_table[hash];
blob_table[hash] = NULL;
/* Put all items into an array for sorting. */
array = xtrycalloc (count, sizeof *array);
if (!array)
{
/* That's bad; give up all items of the bucket. */
log_info ("Note: malloc failed while purging blobs from the "
"cache: %s\n", gpg_strerror (gpg_error_from_syserror ()));
goto leave_drop;
}
narray = 0;
for (b = list_head; b; b = b_next)
{
b_next = b->next;
array[narray++] = b;
b->next = NULL;
}
log_assert (narray == count);
/* Sort the array and put half of it onto a new list. */
qsort (array, narray, sizeof *array, compare_blobs);
list_head = NULL;
list_tailp = &list_head;
for (idx=0; idx < narray/2; idx++)
{
*list_tailp = array[idx];
list_tailp = &array[idx]->next;
}
/* Put the new list into the bucket. */
b = blob_table[hash];
blob_table[hash] = list_head;
list_head = b;
/* Free the remaining items and the array. */
for (; idx < narray; idx++)
{
blob_unref (array[idx]);
blob_table_dropped++;
}
xfree (array);
leave_drop:
/* Free any items added in the meantime by other threads. This
* is also used in case of a malloc problem (which won't update
* the counters, though). */
for ( ; list_head; list_head = b_next)
{
b_next = list_head->next;
blob_unref (list_head);
}
}
/* Add an item to the bucket. We allocate a whole block of items
* for cache performance reasons. */
if (!blob_attic)
{
blob_t b_block;
int b_blocksize = 256;
b_block = xtrymalloc (b_blocksize * sizeof *b_block);
if (!b_block)
{
log_info ("Note: malloc failed while adding blob to the cache: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
xfree (blobdatacopy);
return; /* Out of core - ignore. */
}
for (n = 0; n < b_blocksize; n++)
{
b = b_block + n;
b->next = blob_attic;
blob_attic = b;
}
/* During the malloc another thread might have changed the
* bucket. Thus we need to start over. */
goto find_again;
}
/* We now know that there is an item in the attic. Put it into the
* chain. Note that we may not use any system call here. */
b = blob_attic;
blob_attic = b->next;
b->next = NULL;
b->pktype = pktype;
b->data = blobdatacopy;
b->datalen = blobdatalen;
memcpy (b->ubid, ubid, UBID_LEN);
b->usecount = 1;
b->refcount = 1;
b->next = blob_table[hash];
blob_table[hash] = b;
blob_table_added++;
}
/* Given the UBID return a cached blob item. The caller must
* release that item using blob_unref. */
static blob_t
blob_table_get (const unsigned char *ubid)
{
unsigned int hash;
blob_t b;
hash = blob_table_hasher (ubid);
b = find_blob (hash, ubid, NULL);
if (b)
{
b->usecount++;
b->refcount++;
return b; /* Found */
}
return NULL;
}
/* The hash function we use for the key_table. Must not call a system
* function. */
static inline unsigned int
key_table_hasher (u32 kid_l)
{
return kid_l % key_table_size;
}
/* Runtime allocation of the key table. This allows us to eventually
* add an option to control the size. */
static gpg_error_t
key_table_init (void)
{
if (key_table)
return 0;
key_table_size = NO_OF_KEY_ITEM_BUCKETS;
key_table_threshold = KEY_ITEMS_PER_BUCKET_THRESHOLD;
key_table = xtrycalloc (key_table_size, sizeof *key_table);
if (!key_table)
return gpg_error_from_syserror ();
return 0;
}
/* Free a key_item. This is done by moving it to the attic list. */
static void
key_item_unref (key_item_t ki)
{
bloblist_t bl, bl2;
if (!ki)
return;
log_assert (ki->refcount);
if (!--ki->refcount)
{
bl = ki->blist;
ki->blist = NULL;
ki->next = key_item_attic;
key_item_attic = ki;
if (bl)
{
for (bl2 = bl; bl2->next; bl2 = bl2->next)
;
bl2->next = bloblist_attic;
bloblist_attic = bl;
}
}
}
/* Given the hash value and the search info, find the key item in the
- * bucket. Return NULL if not found or the key item if fount. Always
+ * bucket. Return NULL if not found or the key item if found. Always
* returns the the number of items searched, which is in the case of a
* not-found the length of the chain. Note that FPR may only be NULL
* if FPRLEN is 0. */
static key_item_t
find_in_chain (unsigned int hash, u32 kid_h, u32 kid_l,
unsigned int *r_count)
{
key_item_t ki = key_table[hash];
unsigned int count = 0;
for (; ki; ki = ki->next, count++)
if (ki->kid_h == kid_h && ki->kid_l == kid_l)
break;
if (r_count)
*r_count = count;
return ki;
}
/* Helper for the qsort in key_table_put. */
static int
compare_key_items (const void *arg_a, const void *arg_b)
{
const key_item_t a = *(const key_item_t *)arg_a;
const key_item_t b = *(const key_item_t *)arg_b;
/* Reverse sort on the usecount. */
if (a->usecount > b->usecount)
return -1;
else if (a->usecount == b->usecount)
return 0;
else
return 1;
}
/* Allocate new key items. They are put to the attic so that the
* caller can take them from there. On allocation failure a note
* is printed and an error returned. */
static gpg_error_t
alloc_more_key_items (void)
{
gpg_error_t err;
key_item_t kiblock, ki;
int kiblocksize = 256;
unsigned int n;
kiblock = xtrymalloc (kiblocksize * sizeof *kiblock);
if (!kiblock)
{
err = gpg_error_from_syserror ();
log_info ("Note: malloc failed while adding to the cache: %s\n",
gpg_strerror (err));
return err;
}
for (n = 0; n < kiblocksize; n++)
{
ki = kiblock + n;
ki->next = key_item_attic;
key_item_attic = ki;
}
return 0;
}
/* Allocate new bloblist items. They are put to the attic so that the
* caller can take them from there. On allocation failure a note is
* printed and an error returned. */
static gpg_error_t
alloc_more_bloblist_items (void)
{
gpg_error_t err;
bloblist_t bl;
bloblist_t blistblock;
int blistblocksize = 256;
unsigned int n;
blistblock = xtrymalloc (blistblocksize * sizeof *blistblock);
if (!blistblock)
{
err = gpg_error_from_syserror ();
log_info ("Note: malloc failed while adding to the cache: %s\n",
gpg_strerror (err));
return err;
}
for (n = 0; n < blistblocksize; n++)
{
bl = blistblock + n;
bl->next = bloblist_attic;
bloblist_attic = bl;
}
return 0;
}
/* Helper for key_table_put. This function assumes that
* bloblist_attaci is not NULL. Returns a new bloblist item. Be
* aware that no system calls may be done - even not log
* functions! */
static bloblist_t
new_bloblist_item (const unsigned char *fpr, unsigned int fprlen,
const unsigned char *ubid, int subkey)
{
bloblist_t bl;
bl = bloblist_attic;
bloblist_attic = bl->next;
bl->next = NULL;
if (ubid)
memcpy (bl->ubid, ubid, UBID_LEN);
else
memset (bl->ubid, 0, UBID_LEN);
bl->ubid_valid = 1;
bl->final_kid = 0;
bl->final_fpr = 0;
bl->subkey = !!subkey;
bl->fprlen = fprlen;
memcpy (bl->fpr, fpr, fprlen);
return bl;
}
/* If the list of key item in the bucken HASH is full remove a couple
* of them. On error a diagnostic is printed and an error code
* return. Note that the error code GPG_ERR_TRUE is returned if any
* flush and thus system calls were done.
*/
static gpg_error_t
maybe_flush_some_key_buckets (unsigned int hash, unsigned int count)
{
gpg_error_t err;
key_item_t ki, list_head, *list_tailp, ki_next;
key_item_t *array;
int narray, idx;
if (count < key_table_threshold)
return 0; /* Nothing to do. */
/* Unlink from the global list so that other threads don't disturb
* us. If another thread adds or removes something only one will be
* the winner. Bad luck for the dropped cache items but after all
* it is just a cache. */
list_head = key_table[hash];
key_table[hash] = NULL;
/* Put all items into an array for sorting. */
array = xtrycalloc (count, sizeof *array);
if (!array)
{
/* That's bad; give up all items of the bucket. */
err = gpg_error_from_syserror ();
log_info ("Note: malloc failed while purging from the cache: %s\n",
gpg_strerror (err));
goto leave;
}
narray = 0;
for (ki = list_head; ki; ki = ki_next)
{
ki_next = ki->next;
array[narray++] = ki;
ki->next = NULL;
}
log_assert (narray == count);
/* Sort the array and put half of it onto a new list. */
qsort (array, narray, sizeof *array, compare_key_items);
list_head = NULL;
list_tailp = &list_head;
for (idx=0; idx < narray/2; idx++)
{
*list_tailp = array[idx];
list_tailp = &array[idx]->next;
}
/* Put the new list into the bucket. */
ki = key_table[hash];
key_table[hash] = list_head;
list_head = ki;
/* Free the remaining items and the array. */
for (; idx < narray; idx++)
{
key_item_unref (array[idx]);
key_table_dropped++;
}
xfree (array);
err = gpg_error (GPG_ERR_TRUE);
leave:
/* Free any items added in the meantime by other threads. This is
* also used in case of a malloc problem (which won't update the
* counters, though). */
for ( ; list_head; list_head = ki_next)
{
ki_next = list_head->next;
key_item_unref (list_head);
}
return err;
}
-/* Thsi is the core of
+/* This is the core of
* key_table_put,
* key_table_put_no_fpr,
* key_table_put_no_kid.
*/
static void
do_key_table_put (u32 kid_h, u32 kid_l,
const unsigned char *fpr, unsigned int fprlen,
const unsigned char *ubid, int subkey)
{
unsigned int hash;
key_item_t ki;
bloblist_t bl, bl_tail;
unsigned int count;
int do_find_again;
int mark_not_found = !fpr;
hash = key_table_hasher (kid_l);
find_again:
do_find_again = 0;
ki = find_in_chain (hash, kid_h, kid_l, &count);
if (ki)
{
if (mark_not_found)
return; /* Can't put the mark because meanwhile a entry was
* added. */
for (bl = ki->blist; bl; bl = bl->next)
if (bl->fprlen
&& bl->fprlen == fprlen
&& !memcmp (bl->fpr, fpr, fprlen))
break;
if (bl)
return; /* Already in the bloblist for the keyid */
/* Append to the list. */
if (!bloblist_attic)
{
if (alloc_more_bloblist_items ())
return; /* Out of core - ignore. */
goto find_again; /* Need to start over due to the malloc. */
}
for (bl_tail = NULL, bl = ki->blist; bl; bl_tail = bl, bl = bl->next)
;
bl = new_bloblist_item (fpr, fprlen, ubid, subkey);
if (bl_tail)
bl_tail->next = bl;
else
ki->blist = bl;
return;
}
/* If the bucket is full remove a couple of items. */
if (maybe_flush_some_key_buckets (hash, count))
{
- /* During the fucntion call another thread might have changed
+ /* During the function call another thread might have changed
* the bucket. Thus we need to start over. */
do_find_again = 1;
}
if (!key_item_attic)
{
if (alloc_more_key_items ())
return; /* Out of core - ignore. */
do_find_again = 1;
}
if (!bloblist_attic)
{
if (alloc_more_bloblist_items ())
return; /* Out of core - ignore. */
do_find_again = 1;
}
if (do_find_again)
goto find_again;
/* We now know that there are items in the attics. Put them into
* the chain. Note that we may not use any system call here. */
ki = key_item_attic;
key_item_attic = ki->next;
ki->next = NULL;
if (mark_not_found)
ki->blist = NULL;
else
ki->blist = new_bloblist_item (fpr, fprlen, ubid, subkey);
ki->kid_h = kid_h;
ki->kid_l = kid_l;
ki->usecount = 1;
ki->refcount = 1;
ki->next = key_table[hash];
key_table[hash] = ki;
key_table_added++;
}
/* Given the fingerprint (FPR,FPRLEN) put the UBID into the cache.
* SUBKEY indicates that the fingerprint is from a subkey. */
static void
key_table_put (const unsigned char *fpr, unsigned int fprlen,
const unsigned char *ubid, int subkey)
{
u32 kid_h, kid_l;
if (fprlen < 20 || fprlen > 32)
return; /* No support for v3 keys or unknown key versions. */
if (fprlen == 20) /* v4 key */
{
kid_h = buf32_to_u32 (fpr+12);
kid_l = buf32_to_u32 (fpr+16);
}
else /* v5 or later key */
{
kid_h = buf32_to_u32 (fpr);
kid_l = buf32_to_u32 (fpr+4);
}
do_key_table_put (kid_h, kid_l, fpr, fprlen, ubid, subkey);
}
/* Given the fingerprint (FPR,FPRLEN) put a flag into the cache that
* this fingerprint was not found. */
static void
key_table_put_no_fpr (const unsigned char *fpr, unsigned int fprlen)
{
u32 kid_h, kid_l;
if (fprlen < 20 || fprlen > 32)
return; /* No support for v3 keys or unknown key versions. */
if (fprlen == 20) /* v4 key */
{
kid_h = buf32_to_u32 (fpr+12);
kid_l = buf32_to_u32 (fpr+16);
}
else /* v5 or later key */
{
kid_h = buf32_to_u32 (fpr);
kid_l = buf32_to_u32 (fpr+4);
}
/* Note that our not-found chaching is only based on the keyid. */
do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
}
/* Given the keyid (KID_H, KID_L) put a flag into the cache that this
* keyid was not found. */
static void
key_table_put_no_kid (u32 kid_h, u32 kid_l)
{
do_key_table_put (kid_h, kid_l, NULL, 0, NULL, 0);
}
/* Given the keyid or the fingerprint return the key item from the
* cache. The caller must release the result using key_item_unref.
* NULL is returned if not found. */
static key_item_t
key_table_get (u32 kid_h, u32 kid_l)
{
unsigned int hash;
key_item_t ki;
hash = key_table_hasher (kid_l);
ki = find_in_chain (hash, kid_h, kid_l, NULL);
if (ki)
{
ki->usecount++;
ki->refcount++;
return ki; /* Found */
}
return NULL;
}
/* Return a key item by searching for the keyid. The caller must use
* key_item_unref on it. */
static key_item_t
query_by_kid (u32 kid_h, u32 kid_l)
{
return key_table_get (kid_h, kid_l);
}
/* Return a key item by searching for the fingerprint. The caller
* must use key_item_unref on it. Note that the returned key item may
* not actually carry the fingerprint; the caller needs to scan the
* bloblist of the keyitem. We can't do that here because the
* reference counting is done on the keyitem s and thus this needs to
* be returned. */
static key_item_t
query_by_fpr (const unsigned char *fpr, unsigned int fprlen)
{
u32 kid_h, kid_l;
if (fprlen < 20 || fprlen > 32 )
return NULL; /* No support for v3 keys or unknown key versions. */
if (fprlen == 20) /* v4 key */
{
kid_h = buf32_to_u32 (fpr+12);
kid_l = buf32_to_u32 (fpr+16);
}
else /* v5 or later key */
{
kid_h = buf32_to_u32 (fpr);
kid_l = buf32_to_u32 (fpr+4);
}
return key_table_get (kid_h, kid_l);
}
/* Make sure the tables are initialized. */
gpg_error_t
be_cache_initialize (void)
{
gpg_error_t err;
err = blob_table_init ();
if (!err)
err = key_table_init ();
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd)
{
gpg_error_t err;
backend_handle_t hd;
(void)ctrl;
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_CACHE;
hd->backend_id = be_new_backend_id ();
/* Just in case make sure we are initialized. */
err = be_cache_initialize ();
if (err)
goto leave;
*r_hd = hd;
hd = NULL;
leave:
xfree (hd);
return err;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
/* Fixme: Free the key_table. */
xfree (hd);
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. On a cache hit either 0 or
* GPG_ERR_NOT_FOUND is returned. The former returns the item; the
* latter indicates that the cache has known that the item won't be
* found in any databases. On a cache miss GPG_ERR_EOF is
* returned. */
gpg_error_t
be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t reqpart;
unsigned int n;
blob_t b;
key_item_t ki;
bloblist_t bl;
int not_found = 0;
int descidx = 0;
int found_bykid = 0;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_CACHE);
log_assert (request);
err = be_find_request_part (backend_hd, request, &reqpart);
if (err)
goto leave;
if (!desc)
{
/* Reset operation. */
request->last_cached_valid = 0;
request->last_cached_final = 0;
reqpart->cache_seqno.fpr = 0;
reqpart->cache_seqno.kid = 0;
reqpart->cache_seqno.grip = 0;
reqpart->cache_seqno.ubid = 0;
err = 0;
goto leave;
}
for (ki = NULL, n=0; n < ndesc && !ki; n++)
{
descidx = n;
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
ki = query_by_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
if (ki && ki->blist)
{
not_found = 0;
/* Note that in a bloblist all keyids are the same. */
for (n=0, bl = ki->blist; bl; bl = bl->next)
if (n++ == reqpart->cache_seqno.kid)
break;
if (!bl)
{
key_item_unref (ki);
ki = NULL;
}
else
{
found_bykid = 1;
reqpart->cache_seqno.kid++;
}
}
else if (ki)
not_found = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
ki = query_by_fpr (desc[n].u.fpr, desc[n].fprlen);
if (ki && ki->blist)
{
not_found = 0;
for (n=0, bl = ki->blist; bl; bl = bl->next)
if (bl->fprlen
&& bl->fprlen == desc[n].fprlen
&& !memcmp (bl->fpr, desc[n].u.fpr, desc[n].fprlen)
&& n++ == reqpart->cache_seqno.fpr)
break;
if (!bl)
{
key_item_unref (ki);
ki = NULL;
}
else
reqpart->cache_seqno.fpr++;
}
else if (ki)
not_found = 1;
break;
/* case KEYDB_SEARCH_MODE_KEYGRIP: */
/* ki = query_by_grip (desc[n].u.fpr, desc[n].fprlen); */
/* break; */
case KEYDB_SEARCH_MODE_UBID:
/* This is the quite special UBID mode: If this is
* encountered in the search list we will return just this
* one and obviously look only into the blob cache. */
if (reqpart->cache_seqno.ubid)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
b = blob_table_get (desc[n].u.ubid);
if (b)
{
err = be_return_pubkey (ctrl, b->data, b->datalen,
b->pktype, desc[n].u.ubid);
blob_unref (b);
reqpart->cache_seqno.ubid++;
}
else
err = gpg_error (GPG_ERR_EOF);
}
goto leave;
default:
ki = NULL;
break;
}
}
if (not_found)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
key_item_unref (ki);
}
else if (ki)
{
if (bl && bl->ubid_valid)
{
memcpy (request->last_cached_ubid, bl->ubid, UBID_LEN);
request->last_cached_valid = 1;
request->last_cached_fprlen = desc[descidx].fprlen;
memcpy (request->last_cached_fpr,
desc[descidx].u.fpr, desc[descidx].fprlen);
request->last_cached_kid_h = ki->kid_h;
request->last_cached_kid_l = ki->kid_l;
request->last_cached_valid = 1;
if ((bl->final_kid && found_bykid)
|| (bl->final_fpr && !found_bykid))
request->last_cached_final = 1;
else
request->last_cached_final = 0;
b = blob_table_get (bl->ubid);
if (b)
{
err = be_return_pubkey (ctrl, b->data, b->datalen,
PUBKEY_TYPE_OPGP, bl->ubid);
blob_unref (b);
}
else
{
/* FIXME - return a different code so that the caller
* can lookup using the UBID. */
err = gpg_error (GPG_ERR_MISSING_VALUE);
}
}
else if (bl)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
key_item_unref (ki);
}
else
err = gpg_error (GPG_ERR_EOF);
leave:
return err;
}
/* Mark the last cached item as the final item. This is called when
* the actual database returned EOF in respond to a restart from the
* last cached UBID. */
void
be_cache_mark_final (ctrl_t ctrl, db_request_t request)
{
key_item_t ki;
bloblist_t bl, blfound;
(void)ctrl;
log_assert (request);
if (!request->last_cached_valid)
return;
if (!request->last_cached_fprlen) /* Was cached via keyid. */
{
ki = query_by_kid (request->last_cached_kid_h,
request->last_cached_kid_l);
if (ki && (bl = ki->blist))
{
for (blfound=NULL; bl; bl = bl->next)
blfound = bl;
if (blfound)
blfound->final_kid = 1;
}
key_item_unref (ki);
}
else /* Was cached via fingerprint. */
{
ki = query_by_fpr (request->last_cached_fpr,
request->last_cached_fprlen);
if (ki && (bl = ki->blist))
{
for (blfound=NULL; bl; bl = bl->next)
if (bl->fprlen
&& bl->fprlen == request->last_cached_fprlen
&& !memcmp (bl->fpr, request->last_cached_fpr,
request->last_cached_fprlen))
blfound = bl;
if (blfound)
blfound->final_fpr = 1;
}
key_item_unref (ki);
}
request->last_cached_valid = 0;
}
/* Put the key (BLOB,BLOBLEN) of PUBKEY_TYPE into the cache. */
void
be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
const void *blob, unsigned int bloblen,
enum pubkey_types pubkey_type)
{
gpg_error_t err;
(void)ctrl;
if (pubkey_type == PUBKEY_TYPE_OPGP)
{
struct _keybox_openpgp_info info;
struct _keybox_openpgp_key_info *kinfo;
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("cache: error parsing OpenPGP blob: %s\n",
gpg_strerror (err));
return;
}
blob_table_put (ubid, pubkey_type, blob, bloblen);
kinfo = &info.primary;
key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 0);
if (info.nsubkeys)
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next)
key_table_put (kinfo->fpr, kinfo->fprlen, ubid, 1);
_keybox_destroy_openpgp_info (&info);
}
}
/* Put the a non-found mark for PUBKEY_TYPE into the cache. The
* indices are taken from the search descriptors (DESC,NDESC). */
void
be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
unsigned int n;
(void)ctrl;
(void)pubkey_type;
for (n=0; n < ndesc; n++)
{
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
key_table_put_no_kid (desc[n].u.kid[0], desc[n].u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
key_table_put_no_fpr (desc[n].u.fpr, desc[n].fprlen);
break;
default:
break;
}
}
}
diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c
index 1c4b04226..d70b76097 100644
--- a/kbx/backend-kbx.c
+++ b/kbx/backend-kbx.c
@@ -1,455 +1,455 @@
/* backend-kbx.c - Keybox format backend for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "keyboxd.h"
#include "../common/i18n.h"
#include "backend.h"
#include "keybox.h"
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_KBX. */
unsigned int backend_id; /* Always the id of the backend. */
void *token; /* Used to create a new KEYBOX_HANDLE. */
char filename[1];
};
/* Check that the file FILENAME is a valid keybox file which can be
* used here. Common return codes:
*
* 0 := Valid keybox file
* GPG_ERR_ENOENT := No such file
* GPG_ERR_NO_OBJ := File exists with size zero.
* GPG_ERR_INV_OBJ:= File exists but is not a keybox file.
*/
static gpg_error_t
check_kbx_file_magic (const char *filename)
{
gpg_error_t err;
u32 magic;
unsigned char verbuf[4];
estream_t fp;
fp = es_fopen (filename, "rb");
if (!fp)
return gpg_error_from_syserror ();
err = gpg_error (GPG_ERR_INV_OBJ);
if (es_fread (&magic, 4, 1, fp) == 1 )
{
if (es_fread (&verbuf, 4, 1, fp) == 1
&& verbuf[0] == 1
&& es_fread (&magic, 4, 1, fp) == 1
&& !memcmp (&magic, "KBXf", 4))
{
err = 0;
}
}
else /* Maybe empty: Let's create it. */
err = gpg_error (GPG_ERR_NO_OBJ);
es_fclose (fp);
return err;
}
/* Create new keybox file. This can also be used if the keybox
* already exists but has a length of zero. Do not use it in any
* other cases. */
static gpg_error_t
create_keybox (const char *filename)
{
gpg_error_t err;
dotlock_t lockhd = NULL;
estream_t fp;
/* To avoid races with other temporary instances of keyboxd trying
* to create or update the keybox, we do the next stuff in a locked
* state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
err = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
* writable. However, this whole locking stuff does not make
* sense if this is the case. An empty non-writable directory
* with no keybox is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (err));
return err;
}
if ( dotlock_take (lockhd, -1) )
{
err = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
/* Make sure that at least one record is in a new keybox file, so
* that the detection magic will work the next time it is used.
- * We always set the OpenPGP blobs maybe availabale flag. */
+ * We always set the OpenPGP blobs maybe available flag. */
fp = es_fopen (filename, "w+b,mode=-rw-------");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (err));
goto leave;
}
err = _keybox_write_header_blob (NULL, fp, 1);
es_fclose (fp);
if (err)
{
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (err));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
err = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly)
{
gpg_error_t err;
backend_handle_t hd;
void *token;
(void)ctrl;
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd + strlen (filename));
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_KBX;
strcpy (hd->filename, filename);
err = check_kbx_file_magic (filename);
switch (gpg_err_code (err))
{
case 0:
break;
case GPG_ERR_ENOENT:
case GPG_ERR_NO_OBJ:
if (readonly)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
err = create_keybox (filename);
if (err)
goto leave;
break;
default:
goto leave;
}
err = keybox_register_file (filename, 0, &token);
if (err)
goto leave;
hd->backend_id = be_new_backend_id ();
hd->token = token;
*r_hd = hd;
hd = NULL;
leave:
xfree (hd);
return err;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
xfree (hd);
}
void
be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd)
{
keybox_release (kbx_hd);
}
/* Helper for be_find_request_part to initialize a kbx request part. */
gpg_error_t
be_kbx_init_request_part (backend_handle_t backend_hd, db_request_part_t part)
{
part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0);
if (!part->kbx_hd)
return gpg_error_from_syserror ();
return 0;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. */
gpg_error_t
be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t part;
size_t descindex;
unsigned long skipped_long_blobs;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
if (!desc)
err = keybox_search_reset (part->kbx_hd);
else
err = keybox_search (part->kbx_hd, desc, ndesc, KEYBOX_BLOBTYPE_PGP,
&descindex, &skipped_long_blobs);
if (err == -1)
err = gpg_error (GPG_ERR_EOF);
if (desc && !err)
{
/* Successful search operation. */
void *buffer;
size_t buflen;
enum pubkey_types pubkey_type;
unsigned char ubid[UBID_LEN];
err = keybox_get_data (part->kbx_hd, &buffer, &buflen,
&pubkey_type, ubid);
if (err)
goto leave;
err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, ubid);
if (!err)
be_cache_pubkey (ctrl, ubid, buffer, buflen, pubkey_type);
xfree (buffer);
}
leave:
return err;
}
/* Seek in the keybox to the given UBID (if UBID is not NULL) or to
* the primary fingerprint specified by (FPR,FPRLEN). BACKEND_HD is
* the handle for this backend and REQUEST is the current database
* request object. This does a dummy read so that the next search
* operation starts right after that UBID. */
gpg_error_t
be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid)
{
gpg_error_t err;
db_request_part_t part;
size_t descindex;
unsigned long skipped_long_blobs;
KEYDB_SEARCH_DESC desc;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_UBID;
memcpy (desc.u.ubid, ubid, UBID_LEN);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
err = keybox_search_reset (part->kbx_hd);
if (!err)
err = keybox_search (part->kbx_hd, &desc, 1, 0,
&descindex, &skipped_long_blobs);
if (err == -1)
err = gpg_error (GPG_ERR_EOF);
leave:
return err;
}
/* Insert (BLOB,BLOBLEN) into the keybox. BACKEND_HD is the handle
* for this backend and REQUEST is the current database request
* object. */
gpg_error_t
be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
ksba_cert_t cert = NULL;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
if (pktype == PUBKEY_TYPE_OPGP)
err = keybox_insert_keyblock (part->kbx_hd, blob, bloblen);
else if (pktype == PUBKEY_TYPE_X509)
{
unsigned char sha1[20];
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1, blob, bloblen);
err = keybox_insert_cert (part->kbx_hd, cert, sha1);
}
else
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
ksba_cert_release (cert);
return err;
}
/* Update (BLOB,BLOBLEN) in the keybox. BACKEND_HD is the handle for
* this backend and REQUEST is the current database request object. */
gpg_error_t
be_kbx_update (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
ksba_cert_t cert = NULL;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* FIXME: We make use of the fact that we know that the caller
* already did a keybox search. This needs to be made more
* explicit. */
if (pktype == PUBKEY_TYPE_OPGP)
{
err = keybox_update_keyblock (part->kbx_hd, blob, bloblen);
}
else if (pktype == PUBKEY_TYPE_X509)
{
unsigned char sha1[20];
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1, blob, bloblen);
err = keybox_update_cert (part->kbx_hd, cert, sha1);
}
else
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
ksba_cert_release (cert);
return err;
}
/* Delete the blob from the keybox. BACKEND_HD is the handle for
* this backend and REQUEST is the current database request object. */
gpg_error_t
be_kbx_delete (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request)
{
gpg_error_t err;
db_request_part_t part;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
log_assert (request);
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* FIXME: We make use of the fact that we know that the caller
* already did a keybox search. This needs to be made more
* explicit. */
err = keybox_delete (part->kbx_hd);
leave:
return err;
}
diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c
index 5f716e22c..935811fd5 100644
--- a/kbx/backend-sqlite.c
+++ b/kbx/backend-sqlite.c
@@ -1,1288 +1,1288 @@
/* backend-sqlite.c - SQLite based backend for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sqlite3.h>
#include <npth.h>
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/mbox-util.h"
#include "backend.h"
#include "keybox-search-desc.h"
#include "keybox-defs.h" /* (for the openpgp parser) */
/* Add replacement error codes; GPGRT provides SQL error codes from
* version 1.37 on. */
#if GPGRT_VERSION_NUMBER < 0x012500 /* 1.37 */
static GPGRT_INLINE gpg_error_t
gpg_err_code_from_sqlite (int sqlres)
{
return sqlres? 1500 + (sqlres & 0xff) : 0;
}
#define GPG_ERR_SQL_OK 1500
#define GPG_ERR_SQL_ROW 1600
#define GPG_ERR_SQL_DONE 1601
#endif /*GPGRT_VERSION_NUMBER*/
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_SQLITE. */
unsigned int backend_id; /* Always the id of the backend. */
char filename[1];
};
/* Definition of local request data. */
struct be_sqlite_local_s
{
/* The statement object of the current select command. */
sqlite3_stmt *select_stmt;
/* The search mode represented by the current select command. */
KeydbSearchMode select_mode;
/* The select statement has been executed with success. */
int select_done;
/* The last row has already been reached. */
int select_eof;
};
/* The Mutex we use to protect all our SQLite calls. */
static npth_mutex_t database_mutex = NPTH_MUTEX_INITIALIZER;
/* The one and only database handle. */
static sqlite3 *database_hd;
/* A lockfile used make sure only we are accessing the database. */
static dotlock_t database_lock;
static struct
{
const char *sql;
int special;
} table_definitions[] =
{
{ "PRAGMA foreign_keys = ON" },
/* Table to store config values:
* Standard name value pairs:
* dbversion = 1
* created = <ISO time string>
*/
{ "CREATE TABLE IF NOT EXISTS config ("
"name TEXT NOT NULL,"
"value TEXT NOT NULL "
")", 1 },
/* The actual data; either X.509 certificates or OpenPGP
* keyblocks. */
{ "CREATE TABLE IF NOT EXISTS pubkey ("
- /* The 20 octet truncted primary-fpr */
+ /* The 20 octet truncated primary-fpr */
"ubid BLOB NOT NULL PRIMARY KEY,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The OpenPGP keyblock or X.509 certificate. */
"keyblob BLOB NOT NULL"
")" },
/* Table with fingerprints and keyids of OpenPGP and X.509 keys.
* It is also used for the primary key and the X.509 fingerprint
* because we want to be able to use the keyid and keygrip. */
{ "CREATE TABLE IF NOT EXISTS fingerprint ("
"fpr BLOB NOT NULL PRIMARY KEY,"
/* The long keyid as 64 bit integer. */
"kid INTEGER NOT NULL,"
/* The keygrip for this key. */
"keygrip BLOB NOT NULL,"
/* 0 = primary, > 0 = subkey. */
"subkey INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the fingerprint table. */
{ "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" },
/* Table to allow fast access via user ids or mail addresses. */
{ "CREATE TABLE IF NOT EXISTS userid ("
/* The full user id. */
"uid TEXT NOT NULL,"
/* The mail address if available or NULL. */
"addrspec TEXT,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the userid table. */
{ "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" },
{ "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" },
{ "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" }
};
/* Take a lock for accessing SQLite. */
static void
acquire_mutex (void)
{
int res = npth_mutex_lock (&database_mutex);
if (res)
log_fatal ("failed to acquire database lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
/* Release a lock. */
static void
release_mutex (void)
{
int res = npth_mutex_unlock (&database_mutex);
if (res)
log_fatal ("failed to release database db lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
static void
show_sqlstr (const char *sqlstr)
{
if (!opt.verbose)
return;
log_info ("(SQL: %s)\n", sqlstr);
}
static void
show_sqlstmt (sqlite3_stmt *stmt)
{
char *p;
if (!opt.verbose)
return;
p = sqlite3_expanded_sql (stmt);
if (p)
log_info ("(SQL: %s)\n", p);
sqlite3_free (p);
}
static gpg_error_t
diag_prepare_err (int res, const char *sqlstr)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstr (sqlstr);
log_error ("error preparing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_bind_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error binding a value to an SQL statement: %s\n",
sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_step_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
/* We store the keyid in the database as an integer - this function
* converts it from a memory buffer. */
static GPGRT_INLINE sqlite3_int64
kid_from_mem (const unsigned char *keyid)
{
return ( ((uint64_t)keyid[0] << 56)
| ((uint64_t)keyid[1] << 48)
| ((uint64_t)keyid[2] << 40)
| ((uint64_t)keyid[3] << 32)
| ((uint64_t)keyid[4] << 24)
| ((uint64_t)keyid[5] << 16)
| ((uint64_t)keyid[6] << 8)
| ((uint64_t)keyid[7])
);
}
/* We store the keyid in the database as an integer - this function
* converts it from the usual u32[2] array. */
static GPGRT_INLINE sqlite3_int64
kid_from_u32 (u32 *keyid)
{
return (((uint64_t)keyid[0] << 32) | ((uint64_t)keyid[1]) );
}
/* Run an SQL reset on STMT. */
static gpg_error_t
run_sql_reset (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_reset (stmt);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL reset: %s\n", sqlite3_errstr (res));
}
else
err = 0;
return err;
}
/* Run an SQL prepare for SQLSTR and return a statement at R_STMT. */
static gpg_error_t
run_sql_prepare (const char *sqlstr, sqlite3_stmt **r_stmt)
{
gpg_error_t err;
int res;
res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL);
if (res)
err = diag_prepare_err (res, sqlstr);
else
err = 0;
return err;
}
/* Helper to bind a BLOB parameter to a statement. */
static gpg_error_t
run_sql_bind_blob (sqlite3_stmt *stmt, int no,
const void *blob, size_t bloblen)
{
gpg_error_t err;
int res;
res = sqlite3_bind_blob (stmt, no, blob, bloblen, SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind an INTEGER parameter to a statement. */
static gpg_error_t
run_sql_bind_int (sqlite3_stmt *stmt, int no, int value)
{
gpg_error_t err;
int res;
res = sqlite3_bind_int (stmt, no, value);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind an INTEGER64 parameter to a statement. */
static gpg_error_t
run_sql_bind_int64 (sqlite3_stmt *stmt, int no, sqlite3_int64 value)
{
gpg_error_t err;
int res;
res = sqlite3_bind_int64 (stmt, no, value);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. */
static gpg_error_t
run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value)
{
gpg_error_t err;
int res;
res = sqlite3_bind_text (stmt, no, value, value? strlen (value):0,
SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. A non-NULL VALUE is clamped with percent
* signs. */
static gpg_error_t
run_sql_bind_text_like (sqlite3_stmt *stmt, int no, const char *value)
{
gpg_error_t err;
int res;
char *buf;
if (!value)
{
res = sqlite3_bind_null (stmt, no);
buf = NULL;
}
else
{
buf = xtrymalloc (strlen (value) + 2 + 1);
if (!buf)
return gpg_error_from_syserror ();
*buf = '%';
strcpy (buf+1, value);
strcat (buf+1, "%");
res = sqlite3_bind_text (stmt, no, buf, strlen (buf), SQLITE_TRANSIENT);
}
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
xfree (buf);
return err;
}
/* Wrapper around sqlite3_step for use with simple functions. */
static gpg_error_t
run_sql_step (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_step (stmt);
if (res != SQLITE_DONE)
err = diag_step_err (res, stmt);
else
err = 0;
return err;
}
/* Wrapper around sqlite3_step for use with select. This version does
* not print diags for SQLITE_DONE or SQLITE_ROW but returns them as
* gpg error codes. */
static gpg_error_t
run_sql_step_for_select (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_step (stmt);
if (res == SQLITE_DONE || res == SQLITE_ROW)
err = gpg_error (gpg_err_code_from_sqlite (res));
else
{
/* SQL_OK is unexpected for a select so in this case we return
* the OK error code by bypassing the special mapping. */
if (!res)
err = gpg_error (GPG_ERR_SQL_OK);
else
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error running SQL step: %s\n", sqlite3_errstr (res));
}
return err;
}
/* Run the simple SQL statement in SQLSTR. If UBID is not NULL this
* will be bound to :1 in SQLSTR. This command may not be used for
* select or other command which return rows. */
static gpg_error_t
run_sql_statement_bind_ubid (const char *sqlstr, const unsigned char *ubid)
{
gpg_error_t err;
sqlite3_stmt *stmt;
err = run_sql_prepare (sqlstr, &stmt);
if (err)
goto leave;
if (ubid)
{
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
}
err = run_sql_step (stmt);
sqlite3_finalize (stmt);
if (err)
goto leave;
leave:
return err;
}
/* Run the simple SQL statement in SQLSTR. This command may not be used
* for select or other command which return rows. */
static gpg_error_t
run_sql_statement (const char *sqlstr)
{
return run_sql_statement_bind_ubid (sqlstr, NULL);
}
-/* Create and intitialize a new SQL database file if it does not
+/* Create and initialize a new SQL database file if it does not
* exists; else open it and check that all required objects are
* available. */
static gpg_error_t
create_or_open_database (const char *filename)
{
gpg_error_t err;
int res;
int idx;
if (database_hd)
return 0; /* Already initialized. */
acquire_mutex ();
/* To avoid races with other temporary instances of keyboxd trying
* to create or update the database, we run the database with a lock
* file held. */
database_lock = dotlock_create (filename, 0);
if (!database_lock)
{
err = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
* writable. However, this whole locking stuff does not make
* sense if this is the case. An empty non-writable directory
* with no database is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (err));
goto leave;
}
if (dotlock_take (database_lock, -1))
{
err = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
/* Database has not yet been opened. Open or create it, make sure
* the tables exist, and prepare the required statements. We use
* our own locking instead of the more complex serialization sqlite
* would have to do and it avoid that we call
* npth_unprotect/protect. */
res = sqlite3_open_v2 (filename,
&database_hd,
(SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_NOMUTEX),
NULL);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
log_error ("error opening '%s': %s\n", filename, sqlite3_errstr (res));
goto leave;
}
/* Enable extended error codes. */
sqlite3_extended_result_codes (database_hd, 1);
/* Create the tables if needed. */
for (idx=0; idx < DIM(table_definitions); idx++)
{
err = run_sql_statement (table_definitions[idx].sql);
if (err)
goto leave;
if (table_definitions[idx].special == 1)
{
/* Check and create dbversion etc entries. */
// FIXME
}
}
if (!opt.quiet)
log_info (_("database '%s' created\n"), filename);
err = 0;
leave:
if (err)
{
log_error (_("error creating database '%s': %s\n"),
filename, gpg_strerror (err));
dotlock_release (database_lock);
dotlock_destroy (database_lock);
database_lock = NULL;
}
release_mutex ();
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly)
{
gpg_error_t err;
backend_handle_t hd;
(void)ctrl;
(void)readonly; /* FIXME: implement read-only mode. */
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd + strlen (filename));
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_SQLITE;
strcpy (hd->filename, filename);
err = create_or_open_database (filename);
if (err)
goto leave;
hd->backend_id = be_new_backend_id ();
*r_hd = hd;
hd = NULL;
leave:
xfree (hd);
return err;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
xfree (hd);
}
/* Helper for be_find_request_part to initialize a sqlite request part. */
gpg_error_t
be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part)
{
(void)backend_hd;
part->besqlite = xtrycalloc (1, sizeof *part->besqlite);
if (!part->besqlite)
return gpg_error_from_syserror ();
return 0;
}
/* Release local data of a sqlite request part. */
void
be_sqlite_release_local (be_sqlite_local_t ctx)
{
if (ctx->select_stmt)
sqlite3_finalize (ctx->select_stmt);
xfree (ctx);
}
/* Run a select for the search given by (DESC,NDESC). The data is not
* returned but stored in the request item. */
static gpg_error_t
run_select_statement (be_sqlite_local_t ctx,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err = 0;
unsigned int descidx;
descidx = 0; /* Fixme: take from context. */
if (descidx >= ndesc)
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
/* Check whether we can re-use the current select statement. */
if (!ctx->select_stmt)
;
else if (ctx->select_mode != desc[descidx].mode)
{
sqlite3_finalize (ctx->select_stmt);
ctx->select_stmt = NULL;
}
ctx->select_mode = desc[descidx].mode;
/* Prepare the select and bind the parameters. */
if (ctx->select_stmt)
{
err = run_sql_reset (ctx->select_stmt);
if (err)
goto leave;
}
else
err = 0;
switch (desc[descidx].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
err = gpg_error (GPG_ERR_INTERNAL);
break;
case KEYDB_SEARCH_MODE_EXACT:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, userid as u"
" WHERE u.uid = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, userid as u"
" WHERE u.addrspec = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAILSUB:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, userid as u"
" WHERE u.addrspec LIKE ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, userid as u"
" WHERE u.uid LIKE ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
case KEYDB_SEARCH_MODE_ISSUER:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_issuer (blob, desc[n].u.name)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_issuer_sn (blob, desc[n].u.name, */
/* sn_array? sn_array[n].sn : desc[n].sn, */
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_SN:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_SUBJECT:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_subject (blob, desc[n].u.name)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* pk_no = has_short_kid (blob, desc[n].u.kid[1]); */
/* if (pk_no) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.kid = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_int64 (ctx->select_stmt, 1,
kid_from_u32 (desc[descidx].u.kid));
break;
case KEYDB_SEARCH_MODE_FPR:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.fpr = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.fpr, desc[descidx].fprlen);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.keyblob"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.keygrip = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.grip, KEYGRIP_LEN);
break;
case KEYDB_SEARCH_MODE_UBID:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT ubid, type, keyblob"
" FROM pubkey"
" WHERE ubid = ?1",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.ubid, UBID_LEN);
break;
case KEYDB_SEARCH_MODE_FIRST:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT ubid, type, keyblob"
" FROM pubkey ORDER by ubid",
&ctx->select_stmt);
break;
case KEYDB_SEARCH_MODE_NEXT:
err = gpg_error (GPG_ERR_INTERNAL);
break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
break;
}
leave:
return err;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. */
gpg_error_t
be_sqlite_search (ctrl_t ctrl,
backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t part;
be_sqlite_local_t ctx;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
ctx = part->besqlite;
if (!desc)
{
/* Reset */
ctx->select_done = 0;
ctx->select_eof = 0;
err = 0;
goto leave;
}
if (ctx->select_eof)
{
/* Still in EOF state. */
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
if (!ctx->select_done)
{
/* Initial search - run the select. */
err = run_select_statement (ctx, desc, ndesc);
if (err)
goto leave;
ctx->select_done = 1;
}
show_sqlstmt (ctx->select_stmt);
/* SQL select succeeded - get the first or next row. */
err = run_sql_step_for_select (ctx->select_stmt);
if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
{
int n;
const void *ubid, *keyblob;
size_t keybloblen;
enum pubkey_types pubkey_type;
ubid = sqlite3_column_blob (ctx->select_stmt, 0);
n = sqlite3_column_bytes (ctx->select_stmt, 0);
if (!ubid || n < 0)
{
if (!ubid && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: No column (n=%d)\n",n);
goto leave;
}
if (n != UBID_LEN)
{
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: Bad value (n=%d)\n",n);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
n = sqlite3_column_int (ctx->select_stmt, 1);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column TYPE: %s)\n",
gpg_strerror (err));
goto leave;
}
pubkey_type = n;
keyblob = sqlite3_column_blob (ctx->select_stmt, 2);
n = sqlite3_column_bytes (ctx->select_stmt, 2);
if (!keyblob || n < 0)
{
if (!keyblob && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column KEYBLOB: %s\n",
gpg_strerror (err));
goto leave;
}
keybloblen = n;
err = be_return_pubkey (ctrl, keyblob, keybloblen, pubkey_type, ubid);
if (!err)
be_cache_pubkey (ctrl, ubid, keyblob, keybloblen, pubkey_type);
}
else if (gpg_err_code (err) == GPG_ERR_SQL_DONE)
{
/* FIXME: Move on to the next description index. */
err = gpg_error (GPG_ERR_EOF);
ctx->select_eof = 1;
}
else
{
log_assert (err);
}
leave:
release_mutex ();
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the pubkey
* table. */
static gpg_error_t
store_into_pubkey (enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
if (mode == KBXD_STORE_UPDATE)
sqlstr = ("UPDATE pubkey set keyblob = :3, type = :2 WHERE ubid = :1");
else if (mode == KBXD_STORE_INSERT)
sqlstr = ("INSERT INTO pubkey(ubid,type,keyblob) VALUES(:1,:2,:3)");
else /* Auto */
sqlstr = ("INSERT OR REPLACE INTO pubkey(ubid,type,keyblob)"
" VALUES(:1,:2,:3)");
err = run_sql_prepare (sqlstr, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 2, (int)pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, blob, bloblen);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* fingerprint table. */
static gpg_error_t
store_into_fingerprint (const unsigned char *ubid, int subkey,
const unsigned char *keygrip, sqlite3_int64 kid,
const unsigned char *fpr, int fprlen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
sqlstr = ("INSERT OR REPLACE INTO fingerprint(fpr,kid,keygrip,subkey,ubid)"
" VALUES(:1,:2,:3,:4,:5)");
err = run_sql_prepare (sqlstr, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, fpr, fprlen);
if (err)
goto leave;
err = run_sql_bind_int64 (stmt, 2, kid);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, keygrip, KEYGRIP_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 4, subkey);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 5, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* userid table. */
static gpg_error_t
store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
const char *uid)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
char *addrspec = NULL;
sqlstr = ("INSERT OR REPLACE INTO userid(uid,addrspec,type,ubid)"
" VALUES(:1,:2,:3,:4)");
err = run_sql_prepare (sqlstr, &stmt);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 1, uid);
if (err)
goto leave;
addrspec = mailbox_from_userid (uid, 0);
err = run_sql_bind_text (stmt, 2, addrspec);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 3, pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 4, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
xfree (addrspec);
return err;
}
-/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID macthing
+/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID matching
* that blob. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. MODE is the store
* mode. */
gpg_error_t
be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
int got_mutex = 0;
int in_transaction = 0;
int info_valid = 0;
struct _keybox_openpgp_info info;
struct _keybox_openpgp_key_info *kinfo;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
/* Fixme: The code below is duplicated in be_ubid_from_blob - we
* should have only one function and pass the passed info around
* with the BLOB. */
if (be_is_x509_blob (blob, bloblen))
{
/* The UBID is also our fingerprint. */
/* FIXME: Extract keygrip and KID. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
else
{
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
goto leave;
}
info_valid = 1;
log_assert (pktype == PUBKEY_TYPE_OPGP);
log_assert (info.primary.fprlen >= 20);
log_assert (!memcmp (ubid, info.primary.fpr, UBID_LEN));
}
acquire_mutex ();
got_mutex = 1;
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
in_transaction = 1;
err = store_into_pubkey (mode, pktype, ubid, blob, bloblen);
if (err)
goto leave;
/* Delete all related rows so that we can freshly add possibly added
* or changed user ids and subkeys. */
err = run_sql_statement_bind_ubid
("DELETE FROM fingerprint WHERE ubid = :1", ubid);
if (err)
goto leave;
err = run_sql_statement_bind_ubid
("DELETE FROM userid WHERE ubid = :1", ubid);
if (err)
goto leave;
kinfo = &info.primary;
err = store_into_fingerprint (ubid, 0, kinfo->grip,
kid_from_mem (kinfo->keyid),
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
if (info.nsubkeys)
{
int subkey = 1;
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
{
err = store_into_fingerprint (ubid, subkey, kinfo->grip,
kid_from_mem (kinfo->keyid),
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
}
}
if (info.nuids)
{
struct _keybox_openpgp_uid_info *u;
u = &info.uids;
do
{
log_assert (u->off <= bloblen);
log_assert (u->off + u->len <= bloblen);
{
char *uid = xtrymalloc (u->len + 1);
if (!uid)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (uid, (const unsigned char *)blob + u->off, u->len);
uid[u->len] = 0;
/* Note that we ignore embedded zeros in the user id; this
* is what we do all over the place. */
err = store_into_userid (ubid, pktype, uid);
xfree (uid);
}
if (err)
goto leave;
u = u->next;
}
while (u);
}
leave:
if (in_transaction && !err)
err = run_sql_statement ("commit");
else if (in_transaction)
{
if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
if (got_mutex)
release_mutex ();
if (info_valid)
_keybox_destroy_openpgp_info (&info);
return err;
}
/* Delete the blob specified by UBID from the database. BACKEND_HD is
* the handle for this backend and REQUEST is the current database
* request object. */
gpg_error_t
be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
sqlite3_stmt *stmt = NULL;
int in_transaction = 0;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
in_transaction = 1;
err = run_sql_statement_bind_ubid
("DELETE from userid WHERE ubid = :1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from fingerprint WHERE ubid = :1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from pubkey WHERE ubid = :1", ubid);
leave:
if (stmt)
sqlite3_finalize (stmt);
if (in_transaction && !err)
err = run_sql_statement ("commit");
else if (in_transaction)
{
if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
release_mutex ();
return err;
}
diff --git a/kbx/backend.h b/kbx/backend.h
index 092f490bc..70988419a 100644
--- a/kbx/backend.h
+++ b/kbx/backend.h
@@ -1,181 +1,181 @@
/* backend.h - Definitions for keyboxd backends
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef KBX_BACKEND_H
#define KBX_BACKEND_H
#include "keybox-search-desc.h"
/* Forward declaration of the keybox handle type. */
struct keybox_handle;
typedef struct keybox_handle *KEYBOX_HANDLE;
/* The types of the backends. */
enum database_types
{
- DB_TYPE_NONE, /* No database at all (unitialized etc.). */
+ DB_TYPE_NONE, /* No database at all (uninitialized etc.). */
DB_TYPE_CACHE, /* The cache backend (backend-cache.c). */
DB_TYPE_KBX, /* Keybox type database (backend-kbx.c). */
DB_TYPE_SQLITE /* SQLite type database (backend-sqlite.c).*/
};
/* Declaration of the backend handle. Each backend uses its own
* hidden handle structure with the only common thing being that the
* first field is the database_type to help with debugging. */
struct backend_handle_s;
typedef struct backend_handle_s *backend_handle_t;
/* Private data for sqlite requests. */
struct be_sqlite_local_s;
typedef struct be_sqlite_local_s *be_sqlite_local_t;
/* Object to store backend specific database information per database
* handle. */
struct db_request_part_s
{
struct db_request_part_s *next;
/* Id of the backend instance this object pertains to. */
unsigned int backend_id;
/* Local data for a KBX backend or NULL. */
KEYBOX_HANDLE kbx_hd;
/* Local data for a sqlite backend. */
be_sqlite_local_t besqlite;
/* For the CACHE backend the indices into the bloblist for each
* index type. */
struct {
unsigned int fpr;
unsigned int kid;
unsigned int grip;
unsigned int ubid;
} cache_seqno;
};
typedef struct db_request_part_s *db_request_part_t;
/* A database request handle. This keeps per session search
* information as well as a list of per-backend infos. */
struct db_request_s
{
unsigned int any_search:1; /* Any search has been done. */
unsigned int any_found:1; /* Any object has been found. */
unsigned int last_cached_valid:1; /* see below */
unsigned int last_cached_final:1; /* see below */
unsigned int last_cached_fprlen:8;/* see below */
db_request_part_t part;
/* Counter to track the next to be searched database index. */
unsigned int next_dbidx;
/* The last UBID found in the cache and the corresponding keyid and,
* if found via fpr, the fingerprint. For the LAST_CACHED_FPRLEN see
* above. The entry here is only valid if LAST_CACHED_VALID is set;
* if LAST_CACHED_FINAL is also set, this indicates that no further
* database searches are required. */
unsigned char last_cached_ubid[UBID_LEN];
u32 last_cached_kid_h;
u32 last_cached_kid_l;
unsigned char last_cached_fpr[32];
};
/*-- backend-support.c --*/
const char *strdbtype (enum database_types t);
unsigned int be_new_backend_id (void);
void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd);
void be_release_request (db_request_t req);
gpg_error_t be_find_request_part (backend_handle_t backend_hd,
db_request_t request,
db_request_part_t *r_part);
gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
enum pubkey_types pubkey_type,
const unsigned char *ubid);
int be_is_x509_blob (const unsigned char *blob, size_t bloblen);
gpg_error_t be_ubid_from_blob (const void *blob, size_t bloblen,
enum pubkey_types *r_pktype, char *r_ubid);
/*-- backend-cache.c --*/
gpg_error_t be_cache_initialize (void);
gpg_error_t be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd);
void be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd);
gpg_error_t be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
void be_cache_mark_final (ctrl_t ctrl, db_request_t request);
void be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid,
const void *blob, unsigned int bloblen,
enum pubkey_types pubkey_type);
void be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
/*-- backend-kbx.c --*/
gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly);
void be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd);
void be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd);
gpg_error_t be_kbx_init_request_part (backend_handle_t backend_hd,
db_request_part_t part);
gpg_error_t be_kbx_search (ctrl_t ctrl, backend_handle_t hd,
db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
gpg_error_t be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid);
gpg_error_t be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen);
gpg_error_t be_kbx_update (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum pubkey_types pktype,
const void *blob, size_t bloblen);
gpg_error_t be_kbx_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request);
/*-- backend-sqlite.c --*/
gpg_error_t be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly);
void be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd);
gpg_error_t be_sqlite_init_local (backend_handle_t backend_hd,
db_request_part_t part);
void be_sqlite_release_local (be_sqlite_local_t ctx);
gpg_error_t be_sqlite_search (ctrl_t ctrl, backend_handle_t hd,
db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
gpg_error_t be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum kbxd_store_modes mode,
enum pubkey_types pktype,
const unsigned char *ubid,
const void *blob, size_t bloblen);
gpg_error_t be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid);
#endif /*KBX_BACKEND_H*/
diff --git a/kbx/frontend.c b/kbx/frontend.c
index 508bbc072..ea7d2e23f 100644
--- a/kbx/frontend.c
+++ b/kbx/frontend.c
@@ -1,487 +1,487 @@
/* frontend.c - Database fronend code for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "keyboxd.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/userids.h"
#include "backend.h"
#include "frontend.h"
/* An object to keep infos about the database. */
struct
{
enum database_types db_type;
backend_handle_t backend_handle;
} the_database;
/* Take a lock for reading the databases. */
static void
take_read_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Take a lock for reading and writing the databases. */
static void
take_read_write_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Release a lock. It is valid to call this even if no lock has been
* taken in which case this is a nop. */
static void
release_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Set the database to use. Depending on the FILENAME suffix we
* decide which one to use. This function must be called at daemon
* startup because it employs no locking. If FILENAME has no
* directory separator, the file is expected or created below
* "$GNUPGHOME/public-keys.d/". In READONLY mode the file must exists;
* otherwise it is created. */
gpg_error_t
kbxd_set_database (ctrl_t ctrl, const char *filename_arg, int readonly)
{
gpg_error_t err;
char *filename;
enum database_types db_type = 0;
backend_handle_t handle = NULL;
unsigned int n;
/* Do tilde expansion etc. */
if (strchr (filename_arg, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (filename_arg, '/') /* Windows also accepts a slash. */
#endif
)
filename = make_filename (filename_arg, NULL);
else
filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR,
filename_arg, NULL);
/* If this is the first call to the function and the request is not
* for the cache backend, add the cache backend so that it will
* always be the first to be queried. */
if (the_database.db_type)
{
log_error ("error: only one database allowed\n");
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
/* Init the cache. */
err = be_cache_initialize ();
if (err)
goto leave;
n = strlen (filename);
if (db_type)
; /* We already know it. */
else if (n > 4 && !strcmp (filename + n - 4, ".kbx"))
db_type = DB_TYPE_KBX;
else if (n > 3 && !strcmp (filename + n - 3, ".db"))
db_type = DB_TYPE_SQLITE;
else
{
log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (db_type)
{
case DB_TYPE_NONE: /* NOTREACHED */
break;
case DB_TYPE_CACHE:
err = be_cache_add_resource (ctrl, &handle);
break;
case DB_TYPE_KBX:
err = be_kbx_add_resource (ctrl, &handle, filename, readonly);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_add_resource (ctrl, &handle, filename, readonly);
break;
}
if (err)
goto leave;
the_database.db_type = db_type;
the_database.backend_handle = handle;
handle = NULL;
leave:
if (err)
{
log_error ("error setting database '%s': %s\n",
filename, gpg_strerror (err));
be_generic_release_backend (ctrl, handle);
}
xfree (filename);
return err;
}
/* Release all per session objects. */
void
kbxd_release_session_info (ctrl_t ctrl)
{
if (!ctrl)
return;
be_release_request (ctrl->opgp_req);
ctrl->opgp_req = NULL;
be_release_request (ctrl->x509_req);
ctrl->x509_req = NULL;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. If RESET is set, the search state is first reset.
* Only a reset guarantees that changed search description in DESC are
* considered. */
gpg_error_t
kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
int reset)
{
gpg_error_t err;
int i;
db_request_t request;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
if (DBG_LOOKUP)
{
log_debug ("%s: %u search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
/* char *t = keydb_search_desc_dump (&desc[i]); */
/* log_debug ("%s %d: %s\n", __func__, i, t); */
/* xfree (t); */
}
}
take_read_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* If requested do a reset. Using the reset flag is faster than
- * letting the caller do a separate call for an intial reset. */
+ * letting the caller do a separate call for an initial reset. */
if (!desc || reset)
{
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = 0; /* Nothing to do. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, the_database.backend_handle,
request, NULL, 0);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_search (ctrl, the_database.backend_handle,
request, NULL, 0);
break;
default:
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (err)
{
log_error ("error during the %ssearch reset: %s\n",
reset? "initial ":"", gpg_strerror (err));
goto leave;
}
request->any_search = 0;
request->any_found = 0;
request->next_dbidx = 0;
if (!desc) /* Reset only mode */
{
err = 0;
goto leave;
}
}
/* Divert to the backend for the actual search. */
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = be_cache_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
/* Expected error codes from the cache lookup are:
* 0 - found and returned via the cache
* GPG_ERR_NOT_FOUND - marked in the cache as not available
* GPG_ERR_EOF - cache miss. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
break;
default:
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s => %s\n", __func__,
strdbtype (the_database.db_type), gpg_strerror (err));
request->any_search = 1;
if (!err)
{
request->any_found = 1;
}
else if (gpg_err_code (err) == GPG_ERR_EOF)
{
if (the_database.db_type == DB_TYPE_CACHE && request->last_cached_valid)
{
if (request->last_cached_final)
goto leave;
}
request->next_dbidx++;
/* FIXME: We need to see which pubkey type we need to insert. */
be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave (%s)", __func__, err? "not found" : "found");
return err;
}
/* Store; that is insert or update the key (BLOB,BLOBLEN). MODE
* controls whether only updates or only inserts are allowed. */
gpg_error_t
kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen,
enum kbxd_store_modes mode)
{
gpg_error_t err;
db_request_t request;
char ubid[UBID_LEN];
enum pubkey_types pktype;
int insert = 0;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* Check whether to insert or update. */
err = be_ubid_from_blob (blob, bloblen, &pktype, ubid);
if (err)
goto leave;
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
if (!err)
; /* Found - need to update. */
else if (gpg_err_code (err) == GPG_ERR_EOF)
insert = 1; /* Not found - need to insert. */
else
{
log_debug ("%s: searching fingerprint failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (insert)
{
if (mode == KBXD_STORE_UPDATE)
err = gpg_error (GPG_ERR_CONFLICT);
else
err = be_kbx_insert (ctrl, the_database.backend_handle, request,
pktype, blob, bloblen);
}
else /* Update. */
{
if (mode == KBXD_STORE_INSERT)
err = gpg_error (GPG_ERR_CONFLICT);
else
err = be_kbx_update (ctrl, the_database.backend_handle, request,
pktype, blob, bloblen);
}
}
else if (the_database.db_type == DB_TYPE_SQLITE)
{
err = be_sqlite_store (ctrl, the_database.backend_handle, request,
mode, pktype, ubid, blob, bloblen);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
/* Delete; remove the blob identified by UBID. */
gpg_error_t
kbxd_delete (ctrl_t ctrl, const unsigned char *ubid)
{
gpg_error_t err;
db_request_t request;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->opgp_req)
{
ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
if (!ctrl->opgp_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->opgp_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
if (!err)
; /* Found - we can delete. */
else if (gpg_err_code (err) == GPG_ERR_EOF)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
else
{
log_debug ("%s: searching primary fingerprint failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
err = be_kbx_delete (ctrl, the_database.backend_handle, request);
}
else if (the_database.db_type == DB_TYPE_SQLITE)
{
err = be_sqlite_delete (ctrl, the_database.backend_handle, request, ubid);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c
index 5e603320a..4a5e1f48a 100644
--- a/kbx/kbxserver.c
+++ b/kbx/kbxserver.c
@@ -1,875 +1,875 @@
/* kbxserver.c - Handle Assuan commands send to the keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "keyboxd.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/server-help.h"
#include "../common/userids.h"
#include "../common/asshelp.h"
#include "../common/host2net.h"
#include "frontend.h"
#define PARM_ERROR(t) assuan_set_error (ctx, \
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
/**/: gpg_error (e))
/* Control structure per connection. */
struct server_local_s
{
/* Data used to associate an Assuan context with local server data */
assuan_context_t assuan_ctx;
/* The session id (a counter). */
unsigned int session_id;
/* If this flag is set to true this process will be terminated after
* the end of this session. */
int stopme;
/* If the first both flags are set the assuan logging of data lines
* is suppressed. The count variable is used to show the number of
* non-logged bytes. */
size_t inhibit_data_logging_count;
unsigned int inhibit_data_logging : 1;
unsigned int inhibit_data_logging_now : 1;
/* This flag is set if the last search command was called with --more. */
unsigned int search_expecting_more : 1;
/* This flag is set if the last search command was successful. */
unsigned int search_any_found : 1;
/* The first is the current search description as parsed by the
* cmd_search. If more than one pattern is required, cmd_search
* also allocates and sets multi_search_desc and
* multi_search_desc_len. If a search description has ever been
* allocated the allocated size is stored at
* multi_search_desc_size. */
KEYBOX_SEARCH_DESC search_desc;
KEYBOX_SEARCH_DESC *multi_search_desc;
unsigned int multi_search_desc_size;
unsigned int multi_search_desc_len;
/* If not NULL write output to this stream instead of using D lines. */
estream_t outstream;
};
/* Return the assuan contxt from the local server info in CTRL. */
static assuan_context_t
get_assuan_ctx_from_ctrl (ctrl_t ctrl)
{
if (!ctrl || !ctrl->server_local)
return NULL;
return ctrl->server_local->assuan_ctx;
}
/* If OUTPUT has been used prepare the output FD for use. This needs
* to be called by all functions which will in any way use
* kbxd_write_data_line later. Whether the output goes to the output
* stream is decided by this function. */
static gpg_error_t
prepare_outstream (ctrl_t ctrl)
{
int fd;
log_assert (ctrl && ctrl->server_local);
if (ctrl->server_local->outstream)
return 0; /* Already enabled. */
fd = translate_sys2libc_fd
(assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1);
if (fd == -1)
return 0; /* No Output command active. */
ctrl->server_local->outstream = es_fdopen_nc (fd, "w");
if (!ctrl->server_local->outstream)
return gpg_err_code_from_syserror ();
return 0;
}
/* The usual writen function; here with diagnostic output. */
static gpg_error_t
kbxd_writen (estream_t fp, const void *buffer, size_t length)
{
gpg_error_t err;
size_t nwritten;
if (es_write (fp, buffer, length, &nwritten))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
else if (length != nwritten)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error writing OUTPUT: %s\n", "short write");
}
else
err = 0;
return err;
}
/* A wrapper around assuan_send_data which makes debugging the output
* in verbose mode easier. It also takes CTRL as argument. */
gpg_error_t
kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
gpg_error_t err;
if (!ctx) /* Oops - no assuan context. */
return gpg_error (GPG_ERR_NOT_PROCESSED);
/* Write toa file descriptor if enabled. */
if (ctrl && ctrl->server_local && ctrl->server_local->outstream)
{
unsigned char lenbuf[4];
ulongtobuf (lenbuf, size);
err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4);
if (!err)
err = kbxd_writen (ctrl->server_local->outstream, buffer, size);
if (!err && es_fflush (ctrl->server_local->outstream))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
goto leave;
}
/* If we do not want logging, enable it here. */
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
ctrl->server_local->inhibit_data_logging_now = 1;
if (0 && opt.verbose && buffer && size)
{
/* Ease reading of output by limiting the line length. */
size_t n, nbytes;
nbytes = size;
do
{
n = nbytes > 64? 64 : nbytes;
err = assuan_send_data (ctx, buffer, n);
if (err)
{
gpg_err_set_errno (EIO);
goto leave;
}
buffer += n;
nbytes -= n;
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
{
gpg_err_set_errno (EIO);
goto leave;
}
}
while (nbytes);
}
else
{
err = assuan_send_data (ctx, buffer, size);
if (err)
{
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
goto leave;
}
}
leave:
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
{
ctrl->server_local->inhibit_data_logging_count += size;
ctrl->server_local->inhibit_data_logging_now = 0;
}
return err;
}
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err && opt.verbose)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Handle OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static const char hlp_search[] =
"SEARCH [--no-data] [[--more] PATTERN]\n"
"\n"
"Search for the keys identified by PATTERN. With --more more\n"
"patterns to be used for the search are expected with the next\n"
"command. With --no-data only the search status is returned but\n"
"not the actual data. See also \"NEXT\".";
static gpg_error_t
cmd_search (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_more, opt_no_data;
gpg_error_t err;
unsigned int n, k;
opt_no_data = has_option (line, "--no-data");
opt_more = has_option (line, "--more");
line = skip_options (line);
ctrl->server_local->search_any_found = 0;
if (!*line)
{
if (opt_more)
{
err = set_error (GPG_ERR_INV_ARG, "--more but no pattern");
goto leave;
}
else if (!*line && ctrl->server_local->search_expecting_more)
{
/* It would be too surprising to first set a pattern but
* finally add no pattern to search the entire DB. */
err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern");
goto leave;
}
else /* No pattern - return the first item. */
{
memset (&ctrl->server_local->search_desc, 0,
sizeof ctrl->server_local->search_desc);
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST;
}
}
else
{
err = classify_user_id (line, &ctrl->server_local->search_desc, 0);
if (err)
goto leave;
}
if (opt_more || ctrl->server_local->search_expecting_more)
{
/* More pattern are expected - store the current one and return
* success. */
if (!ctrl->server_local->multi_search_desc_size)
{
n = 10;
ctrl->server_local->multi_search_desc
= xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc);
if (!ctrl->server_local->multi_search_desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->multi_search_desc_size = n;
}
if (ctrl->server_local->multi_search_desc_len
== ctrl->server_local->multi_search_desc_size)
{
KEYBOX_SEARCH_DESC *desc;
n = ctrl->server_local->multi_search_desc_size + 10;
desc = xtrycalloc (n, sizeof *desc);
if (!desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (k=0; k < ctrl->server_local->multi_search_desc_size; k++)
desc[k] = ctrl->server_local->multi_search_desc[k];
xfree (ctrl->server_local->multi_search_desc);
ctrl->server_local->multi_search_desc = desc;
ctrl->server_local->multi_search_desc_size = n;
}
/* Actually store. */
ctrl->server_local->multi_search_desc
[ctrl->server_local->multi_search_desc_len++]
= ctrl->server_local->search_desc;
if (opt_more)
{
/* We need to be called aagain with more pattern. */
ctrl->server_local->search_expecting_more = 1;
goto leave;
}
ctrl->server_local->search_expecting_more = 0;
/* Continue with the actual search. */
}
else
ctrl->server_local->multi_search_desc_len = 0;
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 1);
else
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1);
if (err)
goto leave;
/* Set a flag for use by NEXT. */
ctrl->server_local->search_any_found = 1;
leave:
if (err)
ctrl->server_local->multi_search_desc_len = 0;
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_next[] =
"NEXT [--no-data]\n"
"\n"
- "Get the next search result from a previus search.";
+ "Get the next search result from a previous search.";
static gpg_error_t
cmd_next (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_no_data;
gpg_error_t err;
opt_no_data = has_option (line, "--no-data");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (!ctrl->server_local->search_any_found)
{
err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH");
goto leave;
}
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
{
/* The next condition should never be tru but we better handle
* the first/next transition anyway. */
if (ctrl->server_local->multi_search_desc[0].mode
== KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 0);
}
else
{
/* We need to do the transition from first to next here. */
if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0);
}
if (err)
goto leave;
leave:
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_store[] =
"STORE [--update|--insert]\n"
"\n"
"Insert a key into the database. Whether to insert or update\n"
"the key is decided by looking at the primary key's fingerprint.\n"
"With option --update the key must already exist.\n"
"With option --insert the key must not already exist.\n"
"The actual key material is requested by this function using\n"
" INQUIRE BLOB";
static gpg_error_t
cmd_store (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_update, opt_insert;
enum kbxd_store_modes mode;
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
opt_update = has_option (line, "--update");
opt_insert = has_option (line, "--insert");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (opt_update && !opt_insert)
mode = KBXD_STORE_UPDATE;
else if (!opt_update && opt_insert)
mode = KBXD_STORE_INSERT;
else
mode = KBXD_STORE_AUTO;
/* Ask for the key material. */
err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data received. */
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = kbxd_store (ctrl, value, valuelen, mode);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_delete[] =
"DELETE <ubid> \n"
"\n"
"Delete a key into the database. The UBID identifies the key.\n";
static gpg_error_t
cmd_delete (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int n;
unsigned char ubid[UBID_LEN];
line = skip_options (line);
if (!*line)
{
err = set_error (GPG_ERR_INV_ARG, "UBID missing");
goto leave;
}
/* Skip an optional UBID identifier character. */
if (*line == '^' && line[1])
line++;
if ((n=hex2bin (line, ubid, UBID_LEN)) < 0)
{
err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID");
goto leave;
}
if (line[n])
{
err = set_error (GPG_ERR_INV_ARG, "garbage after UBID");
goto leave;
}
err = kbxd_delete (ctrl, ubid);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
"version - Return the version of the program.\n"
"pid - Return the process id of the server.\n"
"socket_name - Return the name of the socket.\n"
"session_id - Return the current session_id.\n"
"getenv NAME - Return value of envvar NAME\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char numbuf[50];
if (!strcmp (line, "version"))
{
const char *s = VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_kbxd_socket_name ();
if (!s)
s = "[none]";
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "session_id"))
{
snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
err = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
err = assuan_send_data (ctx, s, strlen (s));
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
static const char hlp_killkeyboxd[] =
"KILLKEYBOXD\n"
"\n"
"This command allows a user - given sufficient permissions -\n"
"to kill this keyboxd process.\n";
static gpg_error_t
cmd_killkeyboxd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return gpg_error (GPG_ERR_EOF);
}
static const char hlp_reloadkeyboxd[] =
"RELOADKEYBOXD\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadkeyboxd (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
kbxd_sighup_action ();
return 0;
}
static const char hlp_output[] =
"OUTPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to write the output data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used.";
/* Tell the assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SEARCH", cmd_search, hlp_search },
{ "NEXT", cmd_next, hlp_next },
{ "STORE", cmd_store, hlp_store },
{ "DELETE", cmd_delete, hlp_delete },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "OUTPUT", NULL, hlp_output },
{ "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
{ "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd },
{ NULL, NULL }
};
int i, j, rc;
for (i=j=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Note that we do not reset the list of configured keyservers. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
(void)ctrl;
return 0;
}
/* This function is called by our assuan log handler to test whether a
* log message shall really be printed. The function must return
* false to inhibit the logging of MSG. CAT gives the requested log
* category. MSG might be NULL. */
int
kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)cat;
(void)msg;
if (!ctrl || !ctrl->server_local)
return 1; /* Can't decide - allow logging. */
if (!ctrl->server_local->inhibit_data_logging)
return 1; /* Not requested - allow logging. */
/* Disallow logging if *_now is true. */
return !ctrl->server_local->inhibit_data_logging_now;
}
/* Startup the server and run the main command loop. With FD = -1,
* use stdin/stdout. SESSION_ID is either 0 or a unique number
* identifying a session. */
void
kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id)
{
static const char hello[] = "Keyboxd " VERSION " at your service";
static char *hello_line;
int rc;
assuan_context_t ctx;
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
log_error (_("can't allocate control structure: %s\n"),
gpg_strerror (gpg_error_from_syserror ()));
xfree (ctrl);
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error (_("failed to allocate assuan context: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
if (fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd,
(ASSUAN_SOCKET_SERVER_ACCEPTED
|ASSUAN_SOCKET_SERVER_FDPASSING));
}
if (rc)
{
assuan_release (ctx);
log_error (_("failed to initialize the server: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error (_("failed to the register commands with Assuan: %s\n"),
gpg_strerror(rc));
kbxd_exit (2);
}
if (!hello_line)
{
hello_line = xtryasprintf
("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
/*opt.config_filename? opt.config_filename :*/ "[none]",
hello);
}
ctrl->server_local->assuan_ctx = ctx;
assuan_set_pointer (ctx, ctrl);
assuan_set_hello_line (ctx, hello_line);
assuan_register_option_handler (ctx, option_handler);
assuan_register_reset_notify (ctx, reset_notify);
ctrl->server_local->session_id = session_id;
/* The next call enable the use of status_printf. */
set_assuan_context_func (get_assuan_ctx_from_ctrl);
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
break;
if (rc)
{
log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
break;
}
#ifndef HAVE_W32_SYSTEM
if (opt.verbose)
{
assuan_peercred_t peercred;
if (!assuan_get_peercred (ctx, &peercred))
log_info ("connection from process %ld (%ld:%ld)\n",
(long)peercred->pid, (long)peercred->uid,
(long)peercred->gid);
}
#endif
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
assuan_close_output_fd (ctx);
set_assuan_context_func (NULL);
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);
if (ctrl->server_local->stopme)
kbxd_exit (0);
if (ctrl->refcount)
log_error ("oops: connection control structure still referenced (%d)\n",
ctrl->refcount);
else
{
xfree (ctrl->server_local->multi_search_desc);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
}
diff --git a/kbx/keybox-blob.c b/kbx/keybox-blob.c
index b953e8c57..1210f3773 100644
--- a/kbx/keybox-blob.c
+++ b/kbx/keybox-blob.c
@@ -1,1118 +1,1118 @@
/* keybox-blob.c - KBX Blob handling
* Copyright (C) 2000, 2001, 2002, 2003, 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
* The keybox data format
The KeyBox uses an augmented OpenPGP/X.509 key format. This makes
random access to a keyblock/certificate easier and also gives the
opportunity to store additional information (e.g. the fingerprint)
along with the key. All integers are stored in network byte order,
offsets are counted from the beginning of the Blob.
** Overview of blob types
| Byte 4 | Blob type |
|--------+--------------|
| 0 | Empty blob |
| 1 | First blob |
| 2 | OpenPGP blob |
| 3 | X.509 blob |
** The First blob
The first blob of a plain KBX file has a special format:
- u32 Length of this blob
- byte Blob type (1)
- byte Version number (1)
- u16 Header flags
bit 0 - RFU
bit 1 - Is being or has been used for OpenPGP blobs
- b4 Magic 'KBXf'
- u32 RFU
- u32 file_created_at
- u32 last_maintenance_run
- u32 RFU
- u32 RFU
** The OpenPGP and X.509 blobs
The OpenPGP and X.509 blobs are very similar, things which are
X.509 specific are noted like [X.509: xxx]
- u32 Length of this blob (including these 4 bytes)
- byte Blob type
2 = OpenPGP
3 = X509
- byte Version number of this blob type
1 = Blob with 20 byte fingerprints
2 = Blob with 32 byte fingerprints and no keyids.
- u16 Blob flags
bit 0 = contains secret key material (not used)
bit 1 = ephemeral blob (e.g. used while querying external resources)
- u32 Offset to the OpenPGP keyblock or the X.509 DER encoded
certificate
- u32 The length of the keyblock or certificate
- u16 [NKEYS] Number of keys (at least 1!) [X509: always 1]
- u16 Size of the key information structure (at least 28 or 56).
- NKEYS times:
Version 1 blob:
- b20 The fingerprint of the key.
Fingerprints are always 20 bytes, MD5 left padded with zeroes.
- u32 Offset to the n-th key's keyID (a keyID is always 8 byte)
or 0 if not known which is the case only for X.509.
Note that this separate keyid is not anymore used by
gnupg since the support for v3 keys has been removed.
We create this field anyway for backward compatibility with
old EOL-ed versions. Eventually we will completely move
to the version 2 blob format.
- u16 Key flags
bit 0 = qualified signature (not yet implemented}
- u16 RFU
- bN Optional filler up to the specified length of this
structure.
Version 2 blob:
- b32 The fingerprint of the key. This fingerprint is
either 20 or 32 bytes. A 20 byte fingerprint is
right filled with zeroes.
- u16 Key flags
bit 0 = qualified signature (not yet implemented}
bit 7 = 32 byte fingerprint in use.
- u16 RFU
- b20 keygrip
- bN Optional filler up to the specified length of this
structure.
- u16 Size of the serial number (may be zero)
- bN The serial number. N as given above.
- u16 Number of user IDs
- u16 [NUIDS] Size of user ID information structure
- NUIDS times:
For X509, the first user ID is the Issuer, the second the
Subject and the others are subjectAltNames. For OpenPGP we only
store the information from UserID packets here.
- u32 Blob offset to the n-th user ID
- u32 Length of this user ID.
- u16 User ID flags.
(not yet used)
- byte Validity
- byte RFU
- u16 [NSIGS] Number of signatures
- u16 Size of signature information (4)
- NSIGS times:
- u32 Expiration time of signature with some special values.
Since version 2.1.20 these special valuesare not anymore
used for OpenPGP:
- 0x00000000 = not checked
- 0x00000001 = missing key
- 0x00000002 = bad signature
- 0x10000000 = valid and expires at some date in 1978.
- 0xffffffff = valid and does not expire
- u8 Assigned ownertrust [X509: not used]
- u8 All_Validity
OpenPGP: See ../g10/trustdb/TRUST_* [not yet used]
X509: Bit 4 set := key has been revoked.
Note that this value matches TRUST_FLAG_REVOKED
- u16 RFU
- u32 Recheck_after
- u32 Latest timestamp in the keyblock (useful for KS synchronization?)
- u32 Blob created at
- u32 [NRES] Size of reserved space (not including this field)
- bN Reserved space of size NRES for future use.
- bN Arbitrary space for example used to store data which is not
part of the keyblock or certificate. For example the v3 key
IDs go here.
- bN Space for the keyblock or certificate.
- bN RFU. This is the remaining space after keyblock and before
the checksum. Not part of the SHA-1 checksum.
- b20 SHA-1 checksum (useful for KS synchronization?)
Note, that KBX versions before GnuPG 2.1 used an MD5
checksum. However it was only created but never checked.
Thus we do not expect problems if we switch to SHA-1. If
the checksum fails and the first 4 bytes are zero, we can
try again with MD5. SHA-1 has the advantage that it is
faster on CPUs with dedicated SHA-1 support.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include "keybox-defs.h"
#include <gcrypt.h>
#ifdef KEYBOX_WITH_X509
#include <ksba.h>
#endif
#include "../common/gettime.h"
#include "../common/host2net.h"
#define get32(a) buf32_to_ulong ((a))
/* special values of the signature status */
#define SF_NONE(a) ( !(a) )
#define SF_NOKEY(a) ((a) & (1<<0))
#define SF_BAD(a) ((a) & (1<<1))
#define SF_VALID(a) ((a) & (1<<29))
struct membuf {
size_t len;
size_t size;
char *buf;
int out_of_core;
};
struct keyboxblob_key {
char fpr[32];
u32 off_kid;
ulong off_kid_addr;
u16 flags;
u16 fprlen; /* Either 20 or 32 */
};
struct keyboxblob_uid {
u32 off;
ulong off_addr;
char *name; /* used only with x509 */
u32 len;
u16 flags;
byte validity;
};
struct keyid_list {
struct keyid_list *next;
int seqno;
byte kid[8];
};
struct fixup_list {
struct fixup_list *next;
u32 off;
u32 val;
};
struct keyboxblob {
byte *blob;
size_t bloblen;
off_t fileoffset;
/* stuff used only by keybox_create_blob */
unsigned char *serialbuf;
const unsigned char *serial;
size_t seriallen;
int nkeys;
struct keyboxblob_key *keys;
int nuids;
struct keyboxblob_uid *uids;
int nsigs;
u32 *sigs;
struct fixup_list *fixups;
int fixup_out_of_core;
struct keyid_list *temp_kids;
struct membuf bufbuf; /* temporary store for the blob */
struct membuf *buf;
};
/* A simple implementation of a dynamic buffer. Use init_membuf() to
create a buffer, put_membuf to append bytes and get_membuf to
release and return the buffer. Allocation errors are detected but
only returned at the final get_membuf(), this helps not to clutter
the code with out of core checks. */
static void
init_membuf (struct membuf *mb, int initiallen)
{
mb->len = 0;
mb->size = initiallen;
mb->out_of_core = 0;
mb->buf = xtrymalloc (initiallen);
if (!mb->buf)
mb->out_of_core = 1;
}
static void
put_membuf (struct membuf *mb, const void *buf, size_t len)
{
if (mb->out_of_core)
return;
if (mb->len + len >= mb->size)
{
char *p;
mb->size += len + 1024;
p = xtryrealloc (mb->buf, mb->size);
if (!p)
{
mb->out_of_core = 1;
return;
}
mb->buf = p;
}
if (buf)
memcpy (mb->buf + mb->len, buf, len);
else
memset (mb->buf + mb->len, 0, len);
mb->len += len;
}
static void *
get_membuf (struct membuf *mb, size_t *len)
{
char *p;
if (mb->out_of_core)
{
xfree (mb->buf);
mb->buf = NULL;
return NULL;
}
p = mb->buf;
*len = mb->len;
mb->buf = NULL;
mb->out_of_core = 1; /* don't allow a reuse */
return p;
}
static void
put8 (struct membuf *mb, byte a )
{
put_membuf (mb, &a, 1);
}
static void
put16 (struct membuf *mb, u16 a )
{
unsigned char tmp[2];
tmp[0] = a>>8;
tmp[1] = a;
put_membuf (mb, tmp, 2);
}
static void
put32 (struct membuf *mb, u32 a )
{
unsigned char tmp[4];
tmp[0] = a>>24;
tmp[1] = a>>16;
tmp[2] = a>>8;
tmp[3] = a;
put_membuf (mb, tmp, 4);
}
/* Store a value in the fixup list */
static void
add_fixup (KEYBOXBLOB blob, u32 off, u32 val)
{
struct fixup_list *fl;
if (blob->fixup_out_of_core)
return;
fl = xtrycalloc(1, sizeof *fl);
if (!fl)
blob->fixup_out_of_core = 1;
else
{
fl->off = off;
fl->val = val;
fl->next = blob->fixups;
blob->fixups = fl;
}
}
/*
OpenPGP specific stuff
*/
/* We must store the keyid at some place because we can't calculate
the offset yet. This is only used for v3 keyIDs. Function returns
an index value for later fixup or -1 for out of core. The value
must be a non-zero value. */
static int
pgp_temp_store_kid (KEYBOXBLOB blob, struct _keybox_openpgp_key_info *kinfo)
{
struct keyid_list *k, *r;
k = xtrymalloc (sizeof *k);
if (!k)
return -1;
memcpy (k->kid, kinfo->keyid, 8);
k->seqno = 0;
k->next = blob->temp_kids;
blob->temp_kids = k;
for (r=k; r; r = r->next)
k->seqno++;
return k->seqno;
}
/* Helper for pgp_create_key_part. */
static gpg_error_t
pgp_create_key_part_single (KEYBOXBLOB blob, int n,
struct _keybox_openpgp_key_info *kinfo)
{
size_t fprlen;
int off;
fprlen = kinfo->fprlen;
memcpy (blob->keys[n].fpr, kinfo->fpr, fprlen);
blob->keys[n].fprlen = fprlen;
if (fprlen < 20) /* v3 fpr - shift right and fill with zeroes. */
{
memmove (blob->keys[n].fpr + 20 - fprlen, blob->keys[n].fpr, fprlen);
memset (blob->keys[n].fpr, 0, 20 - fprlen);
off = pgp_temp_store_kid (blob, kinfo);
if (off == -1)
return gpg_error_from_syserror ();
blob->keys[n].off_kid = off;
}
else
blob->keys[n].off_kid = 0; /* Will be fixed up later */
blob->keys[n].flags = 0;
return 0;
}
static gpg_error_t
pgp_create_key_part (KEYBOXBLOB blob, keybox_openpgp_info_t info)
{
gpg_error_t err;
int n = 0;
struct _keybox_openpgp_key_info *kinfo;
err = pgp_create_key_part_single (blob, n++, &info->primary);
if (err)
return err;
if (info->nsubkeys)
for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next)
if ((err=pgp_create_key_part_single (blob, n++, kinfo)))
return err;
assert (n == blob->nkeys);
return 0;
}
static void
pgp_create_uid_part (KEYBOXBLOB blob, keybox_openpgp_info_t info)
{
int n = 0;
struct _keybox_openpgp_uid_info *u;
if (info->nuids)
{
for (u = &info->uids; u; u = u->next)
{
blob->uids[n].off = u->off;
blob->uids[n].len = u->len;
blob->uids[n].flags = 0;
blob->uids[n].validity = 0;
n++;
}
}
assert (n == blob->nuids);
}
static void
pgp_create_sig_part (KEYBOXBLOB blob, u32 *sigstatus)
{
int n;
for (n=0; n < blob->nsigs; n++)
{
blob->sigs[n] = sigstatus? sigstatus[n+1] : 0;
}
}
static int
pgp_create_blob_keyblock (KEYBOXBLOB blob,
const unsigned char *image, size_t imagelen)
{
struct membuf *a = blob->buf;
int n;
u32 kbstart = a->len;
add_fixup (blob, 8, kbstart);
for (n = 0; n < blob->nuids; n++)
add_fixup (blob, blob->uids[n].off_addr, kbstart + blob->uids[n].off);
put_membuf (a, image, imagelen);
add_fixup (blob, 12, a->len - kbstart);
return 0;
}
#ifdef KEYBOX_WITH_X509
/*
X.509 specific stuff
*/
/* Write the raw certificate out */
static int
x509_create_blob_cert (KEYBOXBLOB blob, ksba_cert_t cert)
{
struct membuf *a = blob->buf;
const unsigned char *image;
size_t length;
u32 kbstart = a->len;
/* Store our offset for later fixup */
add_fixup (blob, 8, kbstart);
image = ksba_cert_get_image (cert, &length);
if (!image)
return gpg_error (GPG_ERR_GENERAL);
put_membuf (a, image, length);
add_fixup (blob, 12, a->len - kbstart);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/* Write a stored keyID out to the buffer */
static void
write_stored_kid (KEYBOXBLOB blob, int seqno)
{
struct keyid_list *r;
for ( r = blob->temp_kids; r; r = r->next )
{
if (r->seqno == seqno )
{
put_membuf (blob->buf, r->kid, 8);
return;
}
}
never_reached ();
}
/* Release a list of key IDs */
static void
release_kid_list (struct keyid_list *kl)
{
struct keyid_list *r, *r2;
for ( r = kl; r; r = r2 )
{
r2 = r->next;
xfree (r);
}
}
/* Create a new blob header. If WANT_FPR32 is set a version 2 blob is
* created. */
static int
create_blob_header (KEYBOXBLOB blob, int blobtype, int as_ephemeral,
int want_fpr32)
{
struct membuf *a = blob->buf;
int i;
put32 ( a, 0 ); /* blob length, needs fixup */
put8 ( a, blobtype);
put8 ( a, want_fpr32? 2:1 ); /* blob type version */
put16 ( a, as_ephemeral? 6:4 ); /* blob flags */
put32 ( a, 0 ); /* offset to the raw data, needs fixup */
put32 ( a, 0 ); /* length of the raw data, needs fixup */
put16 ( a, blob->nkeys );
if (want_fpr32)
put16 ( a, 32 + 2 + 2 + 20); /* size of key info */
else
put16 ( a, 20 + 4 + 2 + 2 ); /* size of key info */
for ( i=0; i < blob->nkeys; i++ )
{
if (want_fpr32)
{
put_membuf (a, blob->keys[i].fpr, blob->keys[i].fprlen);
blob->keys[i].off_kid_addr = a->len;
if (blob->keys[i].fprlen == 32)
put16 ( a, (blob->keys[i].flags | 0x80));
else
put16 ( a, blob->keys[i].flags);
put16 ( a, 0 ); /* reserved */
/* FIXME: Put the real grip here instead of the filler. */
put_membuf (a, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 20);
}
else
{
log_assert (blob->keys[i].fprlen <= 20);
put_membuf (a, blob->keys[i].fpr, 20);
blob->keys[i].off_kid_addr = a->len;
put32 ( a, 0 ); /* offset to keyid, fixed up later */
put16 ( a, blob->keys[i].flags );
put16 ( a, 0 ); /* reserved */
}
}
put16 (a, blob->seriallen); /*fixme: check that it fits into 16 bits*/
if (blob->serial)
put_membuf (a, blob->serial, blob->seriallen);
put16 ( a, blob->nuids );
put16 ( a, 4 + 4 + 2 + 1 + 1 ); /* size of uid info */
for (i=0; i < blob->nuids; i++)
{
blob->uids[i].off_addr = a->len;
put32 ( a, 0 ); /* offset to userid, fixed up later */
put32 ( a, blob->uids[i].len );
put16 ( a, blob->uids[i].flags );
put8 ( a, 0 ); /* validity */
put8 ( a, 0 ); /* reserved */
}
put16 ( a, blob->nsigs );
put16 ( a, 4 ); /* size of sig info */
for (i=0; i < blob->nsigs; i++)
{
put32 ( a, blob->sigs[i]);
}
put8 ( a, 0 ); /* assigned ownertrust */
put8 ( a, 0 ); /* validity of all user IDs */
put16 ( a, 0 ); /* reserved */
put32 ( a, 0 ); /* time of next recheck */
put32 ( a, 0 ); /* newest timestamp (none) */
put32 ( a, make_timestamp() ); /* creation time */
put32 ( a, 0 ); /* size of reserved space */
/* reserved space (which is currently of size 0) */
/* space where we write keyIDs and other stuff so that the
pointers can actually point to somewhere */
if (blobtype == KEYBOX_BLOBTYPE_PGP && !want_fpr32)
{
/* For version 1 blobs, we need to store the keyids for all v3
* keys because those key IDs are not part of the fingerprint.
* While we are doing that, we fixup all the keyID offsets. For
* version 2 blobs (which can't carry v3 keys) we compute the
* keyids in the fly because they are just stripped down
* fingerprints. */
for (i=0; i < blob->nkeys; i++ )
{
if (blob->keys[i].off_kid)
{ /* this is a v3 one */
add_fixup (blob, blob->keys[i].off_kid_addr, a->len);
write_stored_kid (blob, blob->keys[i].off_kid);
}
else
{ /* the better v4 key IDs - just store an offset 8 bytes back */
add_fixup (blob, blob->keys[i].off_kid_addr,
blob->keys[i].off_kid_addr - 8);
}
}
}
if (blobtype == KEYBOX_BLOBTYPE_X509)
{
/* We don't want to point to ASN.1 encoded UserIDs (DNs) but to
the utf-8 string representation of them */
for (i=0; i < blob->nuids; i++ )
{
if (blob->uids[i].name)
{ /* this is a v3 one */
add_fixup (blob, blob->uids[i].off_addr, a->len);
put_membuf (blob->buf, blob->uids[i].name, blob->uids[i].len);
}
}
}
return 0;
}
static int
create_blob_trailer (KEYBOXBLOB blob)
{
(void)blob;
return 0;
}
static int
create_blob_finish (KEYBOXBLOB blob)
{
struct membuf *a = blob->buf;
unsigned char *p;
unsigned char *pp;
size_t n;
/* Write placeholders for the checksum. */
put_membuf (a, NULL, 20);
/* get the memory area */
n = 0; /* (Just to avoid compiler warning.) */
p = get_membuf (a, &n);
if (!p)
return gpg_error (GPG_ERR_ENOMEM);
assert (n >= 20);
/* fixup the length */
add_fixup (blob, 0, n);
/* do the fixups */
if (blob->fixup_out_of_core)
{
xfree (p);
return gpg_error (GPG_ERR_ENOMEM);
}
{
struct fixup_list *fl, *next;
for (fl = blob->fixups; fl; fl = next)
{
assert (fl->off+4 <= n);
p[fl->off+0] = fl->val >> 24;
p[fl->off+1] = fl->val >> 16;
p[fl->off+2] = fl->val >> 8;
p[fl->off+3] = fl->val;
next = fl->next;
xfree (fl);
}
blob->fixups = NULL;
}
/* Compute and store the SHA-1 checksum. */
gcry_md_hash_buffer (GCRY_MD_SHA1, p + n - 20, p, n - 40);
pp = xtrymalloc (n);
if ( !pp )
{
xfree (p);
return gpg_error_from_syserror ();
}
memcpy (pp , p, n);
xfree (p);
blob->blob = pp;
blob->bloblen = n;
return 0;
}
gpg_error_t
_keybox_create_openpgp_blob (KEYBOXBLOB *r_blob,
keybox_openpgp_info_t info,
const unsigned char *image,
size_t imagelen,
int as_ephemeral)
{
gpg_error_t err;
KEYBOXBLOB blob;
int need_fpr32 = 0;
*r_blob = NULL;
/* Check whether we need a blob with 32 bit fingerprints. We could
- * use this always but for backward compatiblity we do this only for
+ * use this always but for backward compatibility we do this only for
* v5 keys. */
if (info->primary.version == 5)
need_fpr32 = 1;
else
{
struct _keybox_openpgp_key_info *kinfo;
for (kinfo = &info->subkeys; kinfo; kinfo = kinfo->next)
if (kinfo->version == 5)
{
need_fpr32 = 1;
break;
}
}
blob = xtrycalloc (1, sizeof *blob);
if (!blob)
return gpg_error_from_syserror ();
blob->nkeys = 1 + info->nsubkeys;
blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
if (!blob->keys)
{
err = gpg_error_from_syserror ();
goto leave;
}
blob->nuids = info->nuids;
if (blob->nuids)
{
blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
if (!blob->uids)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
blob->nsigs = info->nsigs;
if (blob->nsigs)
{
blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
if (!blob->sigs)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = pgp_create_key_part (blob, info);
if (err)
goto leave;
pgp_create_uid_part (blob, info);
pgp_create_sig_part (blob, NULL);
init_membuf (&blob->bufbuf, 1024);
blob->buf = &blob->bufbuf;
err = create_blob_header (blob, KEYBOX_BLOBTYPE_PGP,
as_ephemeral, need_fpr32);
if (err)
goto leave;
err = pgp_create_blob_keyblock (blob, image, imagelen);
if (err)
goto leave;
err = create_blob_trailer (blob);
if (err)
goto leave;
err = create_blob_finish (blob);
if (err)
goto leave;
leave:
release_kid_list (blob->temp_kids);
blob->temp_kids = NULL;
if (err)
_keybox_release_blob (blob);
else
*r_blob = blob;
return err;
}
#ifdef KEYBOX_WITH_X509
/* Return an allocated string with the email address extracted from a
DN. Note hat we use this code also in ../sm/keylist.c. */
static char *
x509_email_kludge (const char *name)
{
const char *p, *string;
unsigned char *buf;
int n;
string = name;
for (;;)
{
p = strstr (string, "1.2.840.113549.1.9.1=#");
if (!p)
return NULL;
if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\'))
{
name = p + 22;
break;
}
string = p + 22;
}
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
OpenSSL generated keys get a nicer and usable listing. */
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (!n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; hexdigitp (p); p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return (char*)buf;
}
/* Note: We should move calculation of the digest into libksba and
remove that parameter */
int
_keybox_create_x509_blob (KEYBOXBLOB *r_blob, ksba_cert_t cert,
unsigned char *sha1_digest, int as_ephemeral)
{
int i, rc = 0;
KEYBOXBLOB blob;
unsigned char *sn;
char *p;
char **names = NULL;
size_t max_names;
*r_blob = NULL;
blob = xtrycalloc (1, sizeof *blob);
if( !blob )
return gpg_error_from_syserror ();
sn = ksba_cert_get_serial (cert);
if (sn)
{
size_t n, len;
n = gcry_sexp_canon_len (sn, 0, NULL, NULL);
if (n < 2)
{
xfree (sn);
return gpg_error (GPG_ERR_GENERAL);
}
blob->serialbuf = sn;
sn++; n--; /* skip '(' */
for (len=0; n && *sn && *sn != ':' && digitp (sn); n--, sn++)
len = len*10 + atoi_1 (sn);
if (*sn != ':')
{
xfree (blob->serialbuf);
blob->serialbuf = NULL;
return gpg_error (GPG_ERR_GENERAL);
}
sn++;
blob->serial = sn;
blob->seriallen = len;
}
blob->nkeys = 1;
/* create list of names */
blob->nuids = 0;
max_names = 100;
names = xtrymalloc (max_names * sizeof *names);
if (!names)
{
rc = gpg_error_from_syserror ();
goto leave;
}
p = ksba_cert_get_issuer (cert, 0);
if (!p)
{
rc = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
names[blob->nuids++] = p;
for (i=0; (p = ksba_cert_get_subject (cert, i)); i++)
{
if (blob->nuids >= max_names)
{
char **tmp;
max_names += 100;
tmp = xtryrealloc (names, max_names * sizeof *names);
if (!tmp)
{
rc = gpg_error_from_syserror ();
goto leave;
}
names = tmp;
}
names[blob->nuids++] = p;
if (!i && (p=x509_email_kludge (p)))
names[blob->nuids++] = p; /* due to !i we don't need to check bounds*/
}
/* space for signature information */
blob->nsigs = 1;
blob->keys = xtrycalloc (blob->nkeys, sizeof *blob->keys );
blob->uids = xtrycalloc (blob->nuids, sizeof *blob->uids );
blob->sigs = xtrycalloc (blob->nsigs, sizeof *blob->sigs );
if (!blob->keys || !blob->uids || !blob->sigs)
{
rc = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
memcpy (blob->keys[0].fpr, sha1_digest, 20);
blob->keys[0].off_kid = 0; /* We don't have keyids */
blob->keys[0].flags = 0;
/* issuer and subject names */
for (i=0; i < blob->nuids; i++)
{
blob->uids[i].name = names[i];
blob->uids[i].len = strlen(names[i]);
names[i] = NULL;
blob->uids[i].flags = 0;
blob->uids[i].validity = 0;
}
xfree (names);
names = NULL;
/* signatures */
blob->sigs[0] = 0; /* not yet checked */
/* Create a temporary buffer for further processing */
init_membuf (&blob->bufbuf, 1024);
blob->buf = &blob->bufbuf;
/* write out what we already have */
rc = create_blob_header (blob, KEYBOX_BLOBTYPE_X509, as_ephemeral, 0);
if (rc)
goto leave;
rc = x509_create_blob_cert (blob, cert);
if (rc)
goto leave;
rc = create_blob_trailer (blob);
if (rc)
goto leave;
rc = create_blob_finish ( blob );
if (rc)
goto leave;
leave:
release_kid_list (blob->temp_kids);
blob->temp_kids = NULL;
if (names)
{
for (i=0; i < blob->nuids; i++)
xfree (names[i]);
xfree (names);
}
if (rc)
{
_keybox_release_blob (blob);
*r_blob = NULL;
}
else
{
*r_blob = blob;
}
return rc;
}
#endif /*KEYBOX_WITH_X509*/
int
_keybox_new_blob (KEYBOXBLOB *r_blob,
unsigned char *image, size_t imagelen, off_t off)
{
KEYBOXBLOB blob;
*r_blob = NULL;
blob = xtrycalloc (1, sizeof *blob);
if (!blob)
return gpg_error_from_syserror ();
blob->blob = image;
blob->bloblen = imagelen;
blob->fileoffset = off;
*r_blob = blob;
return 0;
}
void
_keybox_release_blob (KEYBOXBLOB blob)
{
int i;
if (!blob)
return;
if (blob->buf)
{
size_t len;
xfree (get_membuf (blob->buf, &len));
}
xfree (blob->keys );
xfree (blob->serialbuf);
for (i=0; i < blob->nuids; i++)
xfree (blob->uids[i].name);
xfree (blob->uids );
xfree (blob->sigs );
xfree (blob->blob );
xfree (blob );
}
const unsigned char *
_keybox_get_blob_image ( KEYBOXBLOB blob, size_t *n )
{
*n = blob->bloblen;
return blob->blob;
}
off_t
_keybox_get_blob_fileoffset (KEYBOXBLOB blob)
{
return blob->fileoffset;
}
void
_keybox_update_header_blob (KEYBOXBLOB blob, int for_openpgp)
{
if (blob->bloblen >= 32 && blob->blob[4] == KEYBOX_BLOBTYPE_HEADER)
{
u32 val = make_timestamp ();
/* Update the last maintenance run timestamp. */
blob->blob[20] = (val >> 24);
blob->blob[20+1] = (val >> 16);
blob->blob[20+2] = (val >> 8);
blob->blob[20+3] = (val );
if (for_openpgp)
blob->blob[7] |= 0x02; /* OpenPGP data may be available. */
}
}
diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c
index 91d2a1735..66a383bef 100644
--- a/kbx/keybox-search.c
+++ b/kbx/keybox-search.c
@@ -1,1464 +1,1464 @@
/* keybox-search.c - Search operations
* Copyright (C) 2001, 2002, 2003, 2004, 2012,
* 2013 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "keybox-defs.h"
#include <gcrypt.h>
#include "../common/host2net.h"
#include "../common/mbox-util.h"
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
struct sn_array_s {
int snlen;
unsigned char *sn;
};
#define get32(a) buf32_to_ulong ((a))
#define get16(a) buf16_to_ulong ((a))
static inline unsigned int
blob_get_blob_flags (KEYBOXBLOB blob)
{
const unsigned char *buffer;
size_t length;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 8)
return 0; /* oops */
return get16 (buffer + 6);
}
/* Return the first keyid from the blob. Returns true if
available. */
static int
blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid)
{
const unsigned char *buffer;
size_t length, nkeys, keyinfolen;
int fpr32;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 48)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
if (fpr32 && length < 56)
return 0; /* blob to short */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18);
if (!nkeys || keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
if (fpr32 && (get16 (buffer + 20 + 32) & 0x80))
{
/* 32 byte fingerprint. */
kid[0] = get32 (buffer + 20);
kid[1] = get32 (buffer + 20 + 4);
}
else /* 20 byte fingerprint. */
{
kid[0] = get32 (buffer + 20 + 12);
kid[1] = get32 (buffer + 20 + 16);
}
return 1;
}
/* Return information on the flag WHAT within the blob BUFFER,LENGTH.
Return the offset and the length (in bytes) of the flag in
FLAGOFF,FLAG_SIZE. */
gpg_err_code_t
_keybox_get_flag_location (const unsigned char *buffer, size_t length,
int what, size_t *flag_off, size_t *flag_size)
{
size_t pos;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
size_t nsigs, siginfolen, siginfooff;
switch (what)
{
case KEYBOX_FLAG_BLOB:
if (length < 8)
return GPG_ERR_INV_OBJ;
*flag_off = 6;
*flag_size = 2;
break;
case KEYBOX_FLAG_OWNERTRUST:
case KEYBOX_FLAG_VALIDITY:
case KEYBOX_FLAG_CREATED_AT:
case KEYBOX_FLAG_SIG_INFO:
if (length < 20)
return GPG_ERR_INV_OBJ;
/* Key info. */
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return GPG_ERR_INV_OBJ;
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* Serial number. */
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return GPG_ERR_INV_OBJ; /* Out of bounds. */
/* User IDs. */
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 )
return GPG_ERR_INV_OBJ;
pos += uidinfolen*nuids;
if (pos+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
/* Signature info. */
siginfooff = pos;
nsigs = get16 (buffer + pos); pos += 2;
siginfolen = get16 (buffer + pos); pos += 2;
if (siginfolen < 4 )
return GPG_ERR_INV_OBJ;
pos += siginfolen*nsigs;
if (pos+1+1+2+4+4+4+4 > length)
return GPG_ERR_INV_OBJ ; /* Out of bounds. */
*flag_size = 1;
*flag_off = pos;
switch (what)
{
case KEYBOX_FLAG_VALIDITY:
*flag_off += 1;
break;
case KEYBOX_FLAG_CREATED_AT:
*flag_size = 4;
*flag_off += 1+2+4+4+4;
break;
case KEYBOX_FLAG_SIG_INFO:
*flag_size = siginfolen * nsigs;
*flag_off = siginfooff;
break;
default:
break;
}
break;
default:
return GPG_ERR_INV_FLAG;
}
return 0;
}
/* Return one of the flags WHAT in VALUE from the blob BUFFER of
LENGTH bytes. Return 0 on success or an raw error code. */
static gpg_err_code_t
get_flag_from_image (const unsigned char *buffer, size_t length,
int what, unsigned int *value)
{
gpg_err_code_t ec;
size_t pos, size;
*value = 0;
ec = _keybox_get_flag_location (buffer, length, what, &pos, &size);
if (!ec)
switch (size)
{
case 1: *value = buffer[pos]; break;
case 2: *value = get16 (buffer + pos); break;
case 4: *value = get32 (buffer + pos); break;
default: ec = GPG_ERR_BUG; break;
}
return ec;
}
static int
blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
off = pos + 2;
if (off+nserial > length)
return 0; /* out of bounds */
return nserial == snlen && !memcmp (buffer+off, sn, snlen);
}
/* Returns 0 if not found or the number of the key which was found.
For X.509 this is always 1, for OpenPGP this is 1 for the primary
key and 2 and more for the subkeys. */
static int
blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx, fpr32, storedfprlen;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
pos = 20;
if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length)
return 0; /* out of bounds */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (fpr32)
storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20;
else
storedfprlen = 20;
if (storedfprlen == fprlen
&& !memcmp (buffer + off, fpr, storedfprlen))
return idx+1; /* found */
}
return 0; /* not found */
}
/* Helper for has_short_kid and has_long_kid. */
static int
blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr,
int fproff, int fprlen)
{
const unsigned char *buffer;
size_t length;
size_t pos, off;
size_t nkeys, keyinfolen;
int idx, fpr32, storedfprlen;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
pos = 20;
if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length)
return 0; /* out of bounds */
if (fpr32)
fproff = 0; /* keyid are the high-order bits. */
for (idx=0; idx < nkeys; idx++)
{
off = pos + idx*keyinfolen;
if (fpr32)
storedfprlen = (get16 (buffer + off + 32) & 0x80)? 32:20;
else
storedfprlen = 20;
if (storedfprlen == fproff + fprlen
&& !memcmp (buffer + off + fproff, fpr, fprlen))
return idx+1; /* found */
}
return 0; /* not found */
}
/* Returns true if found. */
static int
blob_cmp_ubid (KEYBOXBLOB blob, const unsigned char *ubid)
{
const unsigned char *buffer;
size_t length;
size_t pos;
size_t nkeys, keyinfolen;
int fpr32;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
fpr32 = buffer[5] == 2;
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (!nkeys || keyinfolen < (fpr32?56:28))
return 0; /* invalid blob */
pos = 20;
if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length)
return 0; /* out of bounds */
if (!memcmp (buffer + pos, ubid, UBID_LEN))
return 1; /* found */
return 0; /* not found */
}
static int
blob_cmp_name (KEYBOXBLOB blob, int idx,
const char *name, size_t namelen, int substr, int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if ((uint64_t)pos+2 > (uint64_t)length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (idx < 0)
{ /* Compare all names. Note that for X.509 we start with index 1
so to skip the issuer at index 0. */
for (idx = !!x509; idx < nuids; idx++)
{
size_t mypos = pos;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if ((uint64_t)off+(uint64_t)len > (uint64_t)length)
return 0; /* error: better stop here out of bounds */
if (len < 1)
continue; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
}
else
{
if (idx > nuids)
return 0; /* no user ID with that idx */
pos += idx*uidinfolen;
off = get32 (buffer+pos);
len = get32 (buffer+pos+4);
if (off+len > length)
return 0; /* out of bounds */
if (len < 1)
return 0; /* empty name */
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !memcmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
/* Compare all email addresses of the subject. With SUBSTR given as
True a substring search is done in the mail address. The X509 flag
indicated whether the search is done on an X.509 blob. */
static int
blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr,
int x509)
{
const unsigned char *buffer;
size_t length;
size_t pos, off, len;
size_t nkeys, keyinfolen;
size_t nuids, uidinfolen;
size_t nserial;
int idx;
/* fixme: this code is common to blob_cmp_mail */
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* blob too short */
/*keys*/
nkeys = get16 (buffer + 16);
keyinfolen = get16 (buffer + 18 );
if (keyinfolen < 28)
return 0; /* invalid blob */
pos = 20 + keyinfolen*nkeys;
if (pos+2 > length)
return 0; /* out of bounds */
/*serial*/
nserial = get16 (buffer+pos);
pos += 2 + nserial;
if (pos+4 > length)
return 0; /* out of bounds */
/* user ids*/
nuids = get16 (buffer + pos); pos += 2;
uidinfolen = get16 (buffer + pos); pos += 2;
if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */)
return 0; /* invalid blob */
if (pos + uidinfolen*nuids > length)
return 0; /* out of bounds */
if (namelen < 1)
return 0;
/* Note that for X.509 we start at index 1 because index 0 is used
for the issuer name. */
for (idx=!!x509 ;idx < nuids; idx++)
{
size_t mypos = pos;
size_t mylen;
mypos += idx*uidinfolen;
off = get32 (buffer+mypos);
len = get32 (buffer+mypos+4);
if ((uint64_t)off+(uint64_t)len > (uint64_t)length)
return 0; /* error: better stop here - out of bounds */
if (x509)
{
if (len < 2 || buffer[off] != '<')
continue; /* empty name or trailing 0 not stored */
len--; /* one back */
if ( len < 3 || buffer[off+len] != '>')
continue; /* not a proper email address */
off++;
len--;
}
else /* OpenPGP. */
{
/* We need to forward to the mailbox part. */
mypos = off;
mylen = len;
for ( ; len && buffer[off] != '<'; len--, off++)
;
if (len < 2 || buffer[off] != '<')
{
/* Mailbox not explicitly given or too short. Restore
OFF and LEN and check whether the entire string
resembles a mailbox without the angle brackets. */
off = mypos;
len = mylen;
if (!is_valid_mailbox_mem (buffer+off, len))
continue; /* Not a mail address. */
}
else /* Seems to be standard user id with mail address. */
{
off++; /* Point to first char of the mail address. */
len--;
/* Search closing '>'. */
for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++)
;
if (!len || buffer[mypos] != '>' || off == mypos)
continue; /* Not a proper mail address. */
len = mypos - off;
}
}
if (substr)
{
if (ascii_memcasemem (buffer+off, len, name, namelen))
return idx+1; /* found */
}
else
{
if (len == namelen && !ascii_memcasecmp (buffer+off, name, len))
return idx+1; /* found */
}
}
return 0; /* not found */
}
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
* We don't have the keygrips as meta data, thus we need to parse the
* certificate. Fixme: We might want to return proper error codes
* instead of failing a search for invalid certificates etc. */
static int
blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
int rc = 0;
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
struct _keybox_openpgp_info info;
struct _keybox_openpgp_key_info *k;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* Too short. */
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return 0; /* Too short. */
if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info))
return 0; /* Parse error. */
if (!memcmp (info.primary.grip, grip, 20))
{
rc = 1;
goto leave;
}
if (info.nsubkeys)
{
k = &info.subkeys;
do
{
if (!memcmp (k->grip, grip, 20))
{
rc = 1;
goto leave;
}
k = k->next;
}
while (k);
}
leave:
_keybox_destroy_openpgp_info (&info);
return rc;
}
#ifdef KEYBOX_WITH_X509
/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP.
We don't have the keygrips as meta data, thus we need to parse the
certificate. Fixme: We might want to return proper error codes
instead of failing a search for invalid certificates etc. */
static int
blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip)
{
int rc;
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
ksba_sexp_t p = NULL;
gcry_sexp_t s_pkey;
unsigned char array[20];
unsigned char *rcp;
size_t n;
buffer = _keybox_get_blob_image (blob, &length);
if (length < 40)
return 0; /* Too short. */
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return 0; /* Too short. */
rc = ksba_reader_new (&reader);
if (rc)
return 0; /* Problem with ksba. */
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
goto failed;
rc = ksba_cert_new (&cert);
if (rc)
goto failed;
rc = ksba_cert_read_der (cert, reader);
if (rc)
goto failed;
p = ksba_cert_get_public_key (cert);
if (!p)
goto failed;
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
goto failed;
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
if (rc)
{
gcry_sexp_release (s_pkey);
goto failed;
}
rcp = gcry_pk_get_keygrip (s_pkey, array);
gcry_sexp_release (s_pkey);
if (!rcp)
goto failed; /* Can't calculate keygrip. */
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return !memcmp (array, grip, 20);
failed:
xfree (p);
ksba_cert_release (cert);
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/*
The has_foo functions are used as helpers for search
*/
static inline int
has_short_kid (KEYBOXBLOB blob, u32 lkid)
{
unsigned char buf[4];
buf[0] = lkid >> 24;
buf[1] = lkid >> 16;
buf[2] = lkid >> 8;
buf[3] = lkid;
return blob_cmp_fpr_part (blob, buf, 16, 4);
}
static inline int
has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid)
{
unsigned char buf[8];
buf[0] = mkid >> 24;
buf[1] = mkid >> 16;
buf[2] = mkid >> 8;
buf[3] = mkid;
buf[4] = lkid >> 24;
buf[5] = lkid >> 16;
buf[6] = lkid >> 8;
buf[7] = lkid;
return blob_cmp_fpr_part (blob, buf, 12, 8);
}
static inline int
has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr, unsigned int fprlen)
{
return blob_cmp_fpr (blob, fpr, fprlen);
}
static inline int
has_keygrip (KEYBOXBLOB blob, const unsigned char *grip)
{
if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP)
return blob_openpgp_has_grip (blob, grip);
#ifdef KEYBOX_WITH_X509
if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509)
return blob_x509_has_grip (blob, grip);
#endif
return 0;
}
/* The UBID is the primary fingerprint. For OpenPGP v5 keys only the
* leftmost 20 bytes (UBID_LEN) are used. */
static inline int
has_ubid (KEYBOXBLOB blob, const unsigned char *ubid)
{
return blob_cmp_ubid (blob, ubid);
}
static inline int
has_issuer (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1);
}
static inline int
has_issuer_sn (KEYBOXBLOB blob, const char *name,
const unsigned char *sn, int snlen)
{
size_t namelen;
return_val_if_fail (name, 0);
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return (blob_cmp_sn (blob, sn, snlen)
&& blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1));
}
static inline int
has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen)
{
return_val_if_fail (sn, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
return blob_cmp_sn (blob, sn, snlen);
}
static inline int
has_subject (KEYBOXBLOB blob, const char *name)
{
size_t namelen;
return_val_if_fail (name, 0);
if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1);
}
static inline int
has_username (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
namelen = strlen (name);
return blob_cmp_name (blob, -1 /* all subject/user names */, name,
namelen, substr, (btype == KEYBOX_BLOBTYPE_X509));
}
static inline int
has_mail (KEYBOXBLOB blob, const char *name, int substr)
{
size_t namelen;
int btype;
return_val_if_fail (name, 0);
btype = blob_get_type (blob);
if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509)
return 0;
if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<')
name++; /* Hack to remove the leading '<' for gpg. */
namelen = strlen (name);
if (namelen && name[namelen-1] == '>')
namelen--;
return blob_cmp_mail (blob, name, namelen, substr,
(btype == KEYBOX_BLOBTYPE_X509));
}
static void
release_sn_array (struct sn_array_s *array, size_t size)
{
size_t n;
for (n=0; n < size; n++)
xfree (array[n].sn);
xfree (array);
}
/* Helper to open the file. */
static gpg_error_t
open_file (KEYBOX_HANDLE hd)
{
hd->fp = fopen (hd->kb->fname, "rb");
if (!hd->fp)
{
hd->error = gpg_error_from_syserror ();
return hd->error;
}
return 0;
}
/*
The search API
*/
gpg_error_t
keybox_search_reset (KEYBOX_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
if (hd->fp)
{
if (fseeko (hd->fp, 0, SEEK_SET))
{
/* Ooops. Seek did not work. Close so that the search will
* open the file again. */
fclose (hd->fp);
hd->fp = NULL;
}
}
hd->error = 0;
hd->eof = 0;
return 0;
}
/* Note: When in ephemeral mode the search function does visit all
blobs but in standard mode, blobs flagged as ephemeral are ignored.
If WANT_BLOBTYPE is not 0 only blobs of this type are considered.
The value at R_SKIPPED is updated by the number of skipped long
records (counts PGP and X.509). */
gpg_error_t
keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc,
keybox_blobtype_t want_blobtype,
size_t *r_descindex, unsigned long *r_skipped)
{
gpg_error_t rc;
size_t n;
int need_words, any_skip;
KEYBOXBLOB blob = NULL;
struct sn_array_s *sn_array = NULL;
int pk_no, uid_no;
off_t lastfoundoff;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
- /* Clear last found result but reord the offset of the last found
+ /* Clear last found result but record the offset of the last found
* blob which we may need later. */
if (hd->found.blob)
{
lastfoundoff = _keybox_get_blob_fileoffset (hd->found.blob);
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
else
lastfoundoff = 0;
if (hd->error)
return hd->error; /* still in error state */
if (hd->eof)
return -1; /* still EOF */
/* figure out what information we need */
need_words = any_skip = 0;
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_WORDS:
need_words = 1;
break;
case KEYDB_SEARCH_MODE_FIRST:
/* always restart the search in this mode */
keybox_search_reset (hd);
lastfoundoff = 0;
break;
default:
break;
}
if (desc[n].skipfnc)
any_skip = 1;
if (desc[n].snlen == -1 && !sn_array)
{
sn_array = xtrycalloc (ndesc, sizeof *sn_array);
if (!sn_array)
return (hd->error = gpg_error_from_syserror ());
}
}
(void)need_words; /* Not yet implemented. */
if (!hd->fp)
{
rc = open_file (hd);
if (rc)
{
xfree (sn_array);
return rc;
}
/* log_debug ("%s: re-opened file\n", __func__); */
if (ndesc && desc[0].mode != KEYDB_SEARCH_MODE_FIRST && lastfoundoff)
{
/* Search mode is not first and the last search operation
* returned a blob which also was not the first one. We now
* need to skip over that blob and hope that the file has
* not changed. */
if (fseeko (hd->fp, lastfoundoff, SEEK_SET))
{
rc = gpg_error_from_syserror ();
log_debug ("%s: seeking to last found offset failed: %s\n",
__func__, gpg_strerror (rc));
xfree (sn_array);
return gpg_error (GPG_ERR_NOTHING_FOUND);
}
/* log_debug ("%s: re-opened file and sought to last offset\n", */
/* __func__); */
rc = _keybox_read_blob (NULL, hd->fp, NULL);
if (rc)
{
log_debug ("%s: skipping last found blob failed: %s\n",
__func__, gpg_strerror (rc));
xfree (sn_array);
return gpg_error (GPG_ERR_NOTHING_FOUND);
}
}
}
/* Kludge: We need to convert an SN given as hexstring to its binary
representation - in some cases we are not able to store it in the
search descriptor, because due to the way we use it, it is not
possible to free allocated memory. */
if (sn_array)
{
const unsigned char *s;
int i, odd;
size_t snlen;
for (n=0; n < ndesc; n++)
{
if (!desc[n].sn)
;
else if (desc[n].snlen == -1)
{
unsigned char *sn;
s = desc[n].sn;
for (i=0; *s && *s != '/'; s++, i++)
;
odd = (i & 1);
snlen = (i+1)/2;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
sn = sn_array[n].sn;
s = desc[n].sn;
if (odd)
{
*sn++ = xtoi_1 (s);
s++;
}
for (; *s && *s != '/'; s += 2)
*sn++ = xtoi_2 (s);
}
else
{
const unsigned char *sn;
sn = desc[n].sn;
snlen = desc[n].snlen;
sn_array[n].sn = xtrymalloc (snlen);
if (!sn_array[n].sn)
{
hd->error = gpg_error_from_syserror ();
release_sn_array (sn_array, n);
return hd->error;
}
sn_array[n].snlen = snlen;
memcpy (sn_array[n].sn, sn, snlen);
}
}
}
pk_no = uid_no = 0;
for (;;)
{
unsigned int blobflags;
int blobtype;
_keybox_release_blob (blob); blob = NULL;
rc = _keybox_read_blob (&blob, hd->fp, NULL);
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
++*r_skipped;
continue; /* Skip too large records. */
}
if (rc)
break;
blobtype = blob_get_type (blob);
if (blobtype == KEYBOX_BLOBTYPE_HEADER)
continue;
if (want_blobtype && blobtype != want_blobtype)
continue;
blobflags = blob_get_blob_flags (blob);
if (!hd->ephemeral && (blobflags & 2))
continue; /* Not in ephemeral mode but blob is flagged ephemeral. */
for (n=0; n < ndesc; n++)
{
switch (desc[n].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
break;
case KEYDB_SEARCH_MODE_EXACT:
uid_no = has_username (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAIL:
uid_no = has_mail (blob, desc[n].u.name, 0);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILSUB:
uid_no = has_mail (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_SUBSTR:
uid_no = has_username (blob, desc[n].u.name, 1);
if (uid_no)
goto found;
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
/* not yet implemented */
break;
case KEYDB_SEARCH_MODE_ISSUER:
if (has_issuer (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (has_issuer_sn (blob, desc[n].u.name,
sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SN:
if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn,
sn_array? sn_array[n].snlen : desc[n].snlen))
goto found;
break;
case KEYDB_SEARCH_MODE_SUBJECT:
if (has_subject (blob, desc[n].u.name))
goto found;
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
pk_no = has_short_kid (blob, desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_FPR:
pk_no = has_fingerprint (blob, desc[n].u.fpr, desc[n].fprlen);
if (pk_no)
goto found;
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
if (has_keygrip (blob, desc[n].u.grip))
goto found;
break;
case KEYDB_SEARCH_MODE_UBID:
if (has_ubid (blob, desc[n].u.ubid))
goto found;
break;
case KEYDB_SEARCH_MODE_FIRST:
goto found;
break;
case KEYDB_SEARCH_MODE_NEXT:
goto found;
break;
default:
rc = gpg_error (GPG_ERR_INV_VALUE);
goto found;
}
}
continue;
found:
/* Record which DESC we matched on. Note this value is only
meaningful if this function returns with no errors. */
if(r_descindex)
*r_descindex = n;
for (n=any_skip?0:ndesc; n < ndesc; n++)
{
u32 kid[2];
if (desc[n].skipfnc
&& blob_get_first_keyid (blob, kid)
&& desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no))
break;
}
if (n == ndesc)
break; /* got it */
}
if (!rc)
{
hd->found.blob = blob;
hd->found.pk_no = pk_no;
hd->found.uid_no = uid_no;
}
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF)
{
_keybox_release_blob (blob);
hd->eof = 1;
}
else
{
_keybox_release_blob (blob);
hd->error = rc;
}
if (sn_array)
release_sn_array (sn_array, ndesc);
return rc;
}
/*
* Functions to return a certificate or a keyblock. To be used after
* a successful search operation.
*/
/* Return the raw data from the last found blob. Caller must release
* the value stored at R_BUFFER. If called with NULL for R_BUFFER
* only the needed length for the buffer and the public key type is
* returned. R_PUBKEY_TYPE and R_UBID can be used to return these
* attributes. */
gpg_error_t
keybox_get_data (KEYBOX_HANDLE hd, void **r_buffer, size_t *r_length,
enum pubkey_types *r_pubkey_type, unsigned char *r_ubid)
{
const unsigned char *buffer;
size_t length;
size_t image_off, image_len;
if (r_buffer)
*r_buffer = NULL;
if (r_length)
*r_length = 0;
if (r_pubkey_type)
*r_pubkey_type = PUBKEY_TYPE_UNKNOWN;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
switch (blob_get_type (hd->found.blob))
{
case KEYBOX_BLOBTYPE_PGP:
if (r_pubkey_type)
*r_pubkey_type = PUBKEY_TYPE_OPGP;
break;
case KEYBOX_BLOBTYPE_X509:
if (r_pubkey_type)
*r_pubkey_type = PUBKEY_TYPE_X509;
break;
default:
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
}
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
image_off = get32 (buffer+8);
image_len = get32 (buffer+12);
if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
if (r_ubid)
{
size_t keyinfolen;
/* We do a quick but sufficient consistency check. For the full
* check see blob_cmp_ubid. */
if (!get16 (buffer + 16) /* No keys. */
|| (keyinfolen = get16 (buffer + 18)) < 28
|| (20 + (uint64_t)keyinfolen) > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
memcpy (r_ubid, buffer + 20, UBID_LEN);
}
if (r_length)
*r_length = image_len;
if (r_buffer)
{
*r_buffer = xtrymalloc (image_len);
if (!*r_buffer)
return gpg_error_from_syserror ();
memcpy (*r_buffer, buffer + image_off, image_len);
}
return 0;
}
/* Return the last found keyblock. Returns 0 on success and stores a
* new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to return the
* index of the key or user id which matched the search criteria; if
* not known they are set to 0. */
gpg_error_t
keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf,
int *r_pk_no, int *r_uid_no)
{
gpg_error_t err;
const unsigned char *buffer;
size_t length;
size_t image_off, image_len;
size_t siginfo_off, siginfo_len;
*r_iobuf = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
image_off = get32 (buffer+8);
image_len = get32 (buffer+12);
if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO,
&siginfo_off, &siginfo_len);
if (err)
return err;
*r_pk_no = hd->found.pk_no;
*r_uid_no = hd->found.uid_no;
*r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len);
return 0;
}
#ifdef KEYBOX_WITH_X509
/*
Return the last found cert. Caller must free it.
*/
int
keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert)
{
const unsigned char *buffer;
size_t length;
size_t cert_off, cert_len;
ksba_reader_t reader = NULL;
ksba_cert_t cert = NULL;
int rc;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509)
return gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
if (length < 40)
return gpg_error (GPG_ERR_TOO_SHORT);
cert_off = get32 (buffer+8);
cert_len = get32 (buffer+12);
if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length)
return gpg_error (GPG_ERR_TOO_SHORT);
rc = ksba_reader_new (&reader);
if (rc)
return rc;
rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len);
if (rc)
{
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
rc = ksba_cert_new (&cert);
if (rc)
{
ksba_reader_release (reader);
return rc;
}
rc = ksba_cert_read_der (cert, reader);
if (rc)
{
ksba_cert_release (cert);
ksba_reader_release (reader);
/* fixme: need to map the error codes */
return gpg_error (GPG_ERR_GENERAL);
}
*r_cert = cert;
ksba_reader_release (reader);
return 0;
}
#endif /*KEYBOX_WITH_X509*/
/* Return the flags named WHAT at the address of VALUE. IDX is used
only for certain flags and should be 0 if not required. */
int
keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value)
{
const unsigned char *buffer;
size_t length;
gpg_err_code_t ec;
(void)idx; /* Not yet used. */
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->found.blob)
return gpg_error (GPG_ERR_NOTHING_FOUND);
buffer = _keybox_get_blob_image (hd->found.blob, &length);
ec = get_flag_from_image (buffer, length, what, value);
return ec? gpg_error (ec):0;
}
off_t
keybox_offset (KEYBOX_HANDLE hd)
{
if (!hd->fp)
return 0;
return ftello (hd->fp);
}
gpg_error_t
keybox_seek (KEYBOX_HANDLE hd, off_t offset)
{
gpg_error_t err;
if (hd->error)
return hd->error; /* still in error state */
if (! hd->fp)
{
if (!offset)
{
/* No need to open the file. An unopened file is effectively at
offset 0. */
return 0;
}
err = open_file (hd);
if (err)
return err;
}
err = fseeko (hd->fp, offset, SEEK_SET);
hd->error = gpg_error_from_errno (err);
return hd->error;
}
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
index 9b46565c6..cae86efdf 100644
--- a/kbx/keyboxd.c
+++ b/kbx/keyboxd.c
@@ -1,1848 +1,1848 @@
/* keyboxd.c - The GnuPG Keybox Daemon
* Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc.
* Copyright (C) 2000-2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include <winsock2.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "keyboxd.h"
#include <assuan.h> /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "frontend.h"
/* Urrgs: Put this into a separate header - but it needs assuan.h first. */
extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg);
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oServer,
oDaemon,
oBatch,
oFakedSystemTime,
oListenBacklog,
oDisableCheckOwnSocket,
oDummy
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* The timer tick used for housekeeping stuff. Note that on Windows
* we use a SetWaitableTimer seems to signal earlier than about 2
* seconds. Thus we use 4 seconds on all platforms except for
* Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check
* our own socket in standard socket mode. If that value is 0 we
* don't check at all. All values are in seconds. */
#if defined(HAVE_W32CE_SYSTEM)
# define TIMERTICK_INTERVAL (60)
# define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */
#else
# define TIMERTICK_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
#endif
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Counter for the currently running own socket checks. */
static int check_own_socket_running;
/* Flag to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* Name of the communication socket used for client requests. */
static char *socket_name;
/* We need to keep track of the server's nonces (these are dummies for
* POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Keep track of the current log file so that we can avoid updating
* the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Depending on how keyboxd was started, the homedir inotify watch may
* not be reliable. This flag is set if we assume that inotify works
* reliable. */
static int reliable_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
* Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int cygwin,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void kbxd_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void kbxd_init_default_ctrl (ctrl_t ctrl);
static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd);
static void check_own_socket (void);
static int check_for_running_kbxd (int silent);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
/*
* Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
* This function is expected to be called only once. GETFNC is
* expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
return xstrconcat (libname, " ", getfnc (NULL), NULL);
}
/* Return strings describing this program. The case values are
* described in common/argparse.c:strusage. The values here override
* the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "keyboxd (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: keyboxd [options] (-h for help)");
break;
case 41: p = _("Syntax: keyboxd [options] [command [args]]\n"
"Public key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. Note that we don't fail here, because it is
* important to keep keyboxd running even after re-reading the options
* due to a SIGHUP. */
static void
set_debug (void)
{
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. */
static void
remove_socket (char *name)
{
if (name && *name)
{
gnupg_remove (name);
*name = 0;
}
}
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
if (!inhibit_socket_removal)
remove_socket (socket_name);
}
/* Handle options which are allowed to be reset after program start.
* Return true when the current option in PARGS could be handled and
* false if not. As a special feature, passing a value of NULL for
* PARGS, resets the options to the default. REREAD should be set
* true if it is not the initial option parsing. */
static int
parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
{
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
disable_check_own_socket = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oLogFile:
if (!reread)
- return 0; /* not handeld */
+ return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
}
static void
initialize_modules (void)
{
thread_init_once ();
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
}
/* The main entry point. */
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
unsigned configlineno;
int parse_debug = 0;
int default_config =1;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
char *logfile = NULL;
int gpgconf_list = 0;
int debug_wait = 0;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
* file descriptors and the signal mask. This info is required to
* do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
set_strusage (my_strusage);
log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH);
setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor);
setup_libgcrypt_logging ();
gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL);
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* Yes, a config file was given so we do not try the default
* one. Instead we read the config file when it is
* encountered during main parsing of the command line. */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
}
if (default_config)
configname = make_filename (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf", NULL);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if (parse_debug)
log_info (_("Note: no default option file '%s'\n"), configname);
/* Save the default confif file name so that
* reread_configuration is able to test whether the
* config file has been created in the meantime. */
xfree (config_filename);
config_filename = configname;
configname = NULL;
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror (errno));
exit (2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname);
default_config = 0;
}
while (optfile_parse (configfp, configname, &configlineno, &pargs, opts))
{
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oOptions:
/* Config files may not be nested (silently ignore them). */
if (!configfp)
{
xfree (configname);
configname = xstrdup (pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default : pargs.err = configfp? 1:2; break;
}
}
if (configfp)
{
fclose (configfp);
configfp = NULL;
/* Keep a copy of the name so that it can be read on SIGHUP. */
if (config_filename != configname)
{
xfree (config_filename);
config_filename = configname;
}
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit (2);
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* keyboxd usually does not output any messages because it runs in
* the background. For log files it is acceptable to have messages
* always encoded in utf-8. We switch here to utf-8, so that
* commands like --help still give native messages. It is far
* easier to switch only once instead of for every message and it
* actually helps when more then one thread is active (avoids an
* extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list)
{
/* We have been called without any command and thus we merely
* check whether an instance of us is already running. We do
* this right here so that we don't clobber a logfile with this
* check but print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_kbxd (0);
kbxd_exit (0);
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 2)
kbxd_exit (0);
else if (gpgconf_list)
{
char *filename;
char *filename_esc;
/* List options and default values in the gpgconf format. */
filename = make_filename (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, "keyboxd", GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename);
xfree (filename_esc);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
kbxd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular daemon mode. */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Create the sockets. */
socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 0, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
* we restore it. That is not strictly necessary but some
* programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
exit (0);
/*NOTREACHED*/
} /* End parent */
/*
* This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach)
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
/* Because we don't support running a program on the command
* line we can assume that the inotify things works and thus
* we can avoid the regular stat calls. */
reliable_homedir_inotify = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
{
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
log_info ("%s %s started\n", strusage(11), strusage(13) );
handle_connections (fd);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
kbxd_exit (int rc)
{
/* As usual we run our cleanup handler. */
cleanup ();
/* at this time a bit annoying */
if ((opt.debug & DBG_MEMSTAT_VALUE))
gcry_control (GCRYCTL_DUMP_MEMORY_STATS );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
* set once at startup and dispatches progress messages to the
* corresponding threads of ours. */
static void
kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
* structure usually identified by an argument named CTRL. This
* function is called immediately after allocating the control
* structure. Its purpose is to setup the default values for that
* structure. Note that some values may have already been set. */
static void
kbxd_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to kbxd_init_default_ctrl. */
static void
kbxd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
kbxd_release_session_info (ctrl);
ctrl->magic = 0xdeadbeef;
unregister_progress_cb ();
xfree (ctrl->lc_messages);
}
/* Reread parts of the configuration. Note, that this function is
* obviously not thread-safe and should only be called from the PTH
* signal handler.
*
* Fixme: Due to the way the argument parsing works, we create a
* memory leak here for all string type arguments. There is currently
* no clean way to tell whether the memory for the argument has been
- * allocated or points into the process' original arguments. Unless
+ * allocated or points into the process's original arguments. Unless
* we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
ARGPARSE_ARGS pargs;
FILE *fp;
unsigned int configlineno = 0;
int dummy;
if (!config_filename)
return; /* No config file. */
fp = fopen (config_filename, "r");
if (!fp)
{
log_info (_("option file '%s': %s\n"),
config_filename, strerror(errno) );
return;
}
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = 1; /* do not remove the args */
while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) )
{
if (pargs.r_opt < -1)
pargs.err = 1; /* Print a warning. */
else /* Try to parse this option - ignore unchangeable ones. */
parse_rereadable_options (&pargs, 1);
}
fclose (fp);
finalize_rereadable_options ();
set_debug ();
}
/* Return the file name of the socket we are using for requests. */
const char *
get_kbxd_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_kbxd_active_connection_count (void)
{
return active_connections;
}
/* Create a name for the socket in the home directory as using
* STANDARD_NAME. We also check for valid characters as well as
* against a maximum allowed length for a Unix domain socket is done.
* The function terminates the process in case of an error. The
* function returns a pointer to an allocated string with the absolute
* name of the socket used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
kbxd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
* or terminates the process in case of an error. If CYGWIN is set a
* Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
kbxd_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
if (assuan_sock_set_sockaddr_un (name, addr, NULL))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* Our error code mapping on W32CE returns EEXIST thus we also test
for this. */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a keyboxd is already running. */
if (!check_for_running_kbxd (1))
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a keyboxd is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the public keys exists and
* create it if not. This function won't fail as it is only a
* convenience function and not strictly necessary. */
static void
create_public_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwxr-x"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwxr-x"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
* same as the default one. This way we avoid to create arbitrary
* directories when a non-default home directory is used. To cope
* with HOME, we compare only the suffix if we see that the default
* homedir does start with a tilde. We don't stop here in case of
* problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_public_keys_directory (home);
}
xfree (home);
}
/* This is the worker for the ticker. It is called every few seconds
* and may only do fast operations. */
static void
handle_tick (void)
{
static time_t last_minute;
struct stat statbuf;
if (!last_minute)
last_minute = time (NULL);
/* Code to be run from time to time. */
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL))
{
check_own_socket ();
last_minute = time (NULL);
}
#endif
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& (!have_homedir_inotify || !reliable_homedir_inotify)
&& stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* A global function which allows us to call the reload stuff from
* other places too. This is only used when build for W32. */
void
kbxd_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
reread_configuration ();
}
/* A helper function to handle SIGUSR2. */
static void
kbxd_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - no action\n");
/* Nothing to do right now. */
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
* its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
kbxd_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
break;
case SIGUSR2:
kbxd_sigusr2_action ();
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
kbxd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
kbxd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
static void *
do_start_connection_thread (ctrl_t ctrl)
{
static unsigned int last_session_id;
unsigned int session_id;
active_connections++;
kbxd_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd));
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* Connection handler loop. Wait for connection requests and spawn a
* thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread },
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n", strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#else
# ifdef HAVE_W32CE_SYSTEM
/* Use a dummy event. */
sigs = 0;
ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
# else
events[0] = INVALID_HANDLE_VALUE;
# endif
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (disable_check_own_socket)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2INT (listen_fd);
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new keyboxd instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask ());
saved_errno = errno;
{
int signo;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if ((events_set & 1))
kbxd_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
* next timeout. */
continue;
}
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
(struct sockaddr *)&paddr, &plen));
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* The thread running the actual check. We need to run this in a
* separate thread so that check_own_thread can be called from the
* timer tick. */
static void *
check_own_socket_thread (void *arg)
{
int rc;
char *sockname = arg;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
xfree (sockname);
if (ctx)
assuan_release (ctx);
if (rc)
{
/* We may not remove the socket as it is now in use by another
* server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
check_own_socket_running--;
return NULL;
}
/* Check whether we are still listening on our own socket. In case
* another keyboxd process started after us has taken ownership of our
* socket, we would linger around without any real task. Thus we
* better check once in a while whether we are really needed. */
static void
check_own_socket (void)
{
char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket)
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return; /* Out of memory. */
err = npth_attr_init (&tattr);
if (err)
return;
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, check_own_socket_thread, sockname);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
npth_attr_destroy (&tattr);
}
/* Figure out whether a keyboxd is available and running. Prints an
* error if not. If SILENT is true, no messages are printed. Returns
* 0 if the agent is running. */
static int
check_for_running_kbxd (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no keyboxd running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("keyboxd running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/scd/app-common.h b/scd/app-common.h
index dc5684b39..752e75da2 100644
--- a/scd/app-common.h
+++ b/scd/app-common.h
@@ -1,339 +1,339 @@
/* app-common.h - Common declarations for all card applications
* Copyright (C) 2003, 2005, 2008 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* $Id$
*/
#ifndef GNUPG_SCD_APP_COMMON_H
#define GNUPG_SCD_APP_COMMON_H
#include <npth.h>
#include <ksba.h>
/* Flags used with app_change_pin. */
#define APP_CHANGE_FLAG_RESET 1 /* PIN Reset mode. */
#define APP_CHANGE_FLAG_NULLPIN 2 /* NULL PIN mode. */
#define APP_CHANGE_FLAG_CLEAR 4 /* Clear the given PIN. */
/* Flags used with app_genkey. */
#define APP_GENKEY_FLAG_FORCE 1 /* Force overwriting existing key. */
/* Flags used with app_writekey. */
#define APP_WRITEKEY_FLAG_FORCE 1 /* Force overwriting existing key. */
/* Flags used with app_readkey. */
#define APP_READKEY_FLAG_INFO 1 /* Send also a KEYPAIRINFO line. */
/* Flags set by the decipher function into R_INFO. */
#define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */
/* Flags used by the app_write_learn_status. */
#define APP_LEARN_FLAG_KEYPAIRINFO 1 /* Return only keypair infos. */
#define APP_LEARN_FLAG_MULTI 2 /* Return info for all apps. */
/* List of supported card types. Generic is the usual ISO7817-4
* compliant card. More specific card or token versions can be given
* here. Use strcardtype() to map them to a string. */
typedef enum
{
CARDTYPE_GENERIC = 0,
CARDTYPE_YUBIKEY
} cardtype_t;
/* List of supported card applications. The source code for each
* application can usually be found in an app-NAME.c file. Use
* strapptype() to map them to a string. */
typedef enum
{
APPTYPE_NONE = 0,
APPTYPE_UNDEFINED,
APPTYPE_OPENPGP,
APPTYPE_PIV,
APPTYPE_NKS,
APPTYPE_P15,
APPTYPE_GELDKARTE,
APPTYPE_DINSIG,
APPTYPE_SC_HSM
} apptype_t;
/* Forward declarations. */
struct card_ctx_s;
struct app_ctx_s;
struct app_local_s; /* Defined by all app-*.c. */
typedef struct card_ctx_s *card_t;
typedef struct app_ctx_s *app_t;
/* The object describing a card. */
struct card_ctx_s {
card_t next;
npth_mutex_t lock;
/* Number of connections currently using this application context. */
unsigned int ref_count;
/* Used reader slot. */
int slot;
cardtype_t cardtype; /* The token's type. */
unsigned int cardversion;/* Firmware version of the token or 0. */
unsigned int card_status;
/* The serial number is associated with the card and not with a
* specific app. If a card uses different serial numbers for its
* applications, our code picks the serial number of a specific
* application and uses that. */
unsigned char *serialno; /* Serialnumber in raw form, allocated. */
size_t serialnolen; /* Length in octets of serialnumber. */
/* A linked list of applications used on this card. The app at the
* head of the list is the currently active app; To work with the
* other apps, switching to that app might be needed. Switching will
* put the active app at the head of the list. */
app_t app;
/* Various flags. */
unsigned int reset_requested:1;
unsigned int periodical_check_needed:1;
};
/* The object describing a card's applications. A card may have
- * several applications and it is usuallay required to explicity
+ * several applications and it is usually required to explicitly
* switch between applications. */
struct app_ctx_s {
app_t next;
card_t card; /* Link back to the card. */
apptype_t apptype; /* The type of the application. */
unsigned int appversion; /* Version of the application or 0. */
unsigned int did_chv1:1;
unsigned int force_chv1:1; /* True if the card does not cache CHV1. */
unsigned int did_chv2:1;
unsigned int did_chv3:1;
struct app_local_s *app_local; /* Local to the application. */
struct {
void (*deinit) (app_t app);
gpg_error_t (*prep_reselect) (app_t app, ctrl_t ctrl);
gpg_error_t (*reselect) (app_t app, ctrl_t ctrl);
gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags);
gpg_error_t (*readcert) (app_t app, const char *certid,
unsigned char **cert, size_t *certlen);
gpg_error_t (*readkey) (app_t app, ctrl_t ctrl,
const char *certid, unsigned int flags,
unsigned char **pk, size_t *pklen);
gpg_error_t (*getattr) (app_t app, ctrl_t ctrl, const char *name);
gpg_error_t (*setattr) (app_t app, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen);
gpg_error_t (*sign) (app_t app, ctrl_t ctrl,
const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen );
gpg_error_t (*auth) (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
gpg_error_t (*decipher) (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info);
gpg_error_t (*writecert) (app_t app, ctrl_t ctrl,
const char *certid,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg,
const unsigned char *data, size_t datalen);
gpg_error_t (*writekey) (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg,
const unsigned char *pk, size_t pklen);
gpg_error_t (*genkey) (app_t app, ctrl_t ctrl,
const char *keyref, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t (*change_pin) (app_t app, ctrl_t ctrl,
const char *chvnostr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t (*check_pin) (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t (*with_keygrip) (app_t app, ctrl_t ctrl, int action,
const char *keygrip_str, int capability);
} fnc;
};
/* Action values for app_do_with_keygrip. */
enum
{
KEYGRIP_ACTION_SEND_DATA,
KEYGRIP_ACTION_WRITE_STATUS,
KEYGRIP_ACTION_LOOKUP
};
/* Helper to get the slot from an APP object. */
static inline int
app_get_slot (app_t app)
{
if (app && app->card)
return app->card->slot;
return -1;
}
/*-- app-help.c --*/
unsigned int app_help_count_bits (const unsigned char *a, size_t len);
gpg_error_t app_help_get_keygrip_string_pk (const void *pk, size_t pklen,
char *hexkeygrip);
gpg_error_t app_help_get_keygrip_string (ksba_cert_t cert, char *hexkeygrip);
gpg_error_t app_help_pubkey_from_cert (const void *cert, size_t certlen,
unsigned char **r_pk, size_t *r_pklen);
size_t app_help_read_length_of_cert (int slot, int fid, size_t *r_certoff);
/*-- app.c --*/
const char *strcardtype (cardtype_t t);
const char *strapptype (apptype_t t);
void app_update_priority_list (const char *arg);
gpg_error_t app_send_card_list (ctrl_t ctrl);
gpg_error_t app_send_active_apps (card_t card, ctrl_t ctrl);
char *card_get_serialno (card_t card);
char *app_get_serialno (app_t app);
void app_dump_state (void);
void application_notify_card_reset (int slot);
gpg_error_t check_application_conflict (card_t card, const char *name,
const unsigned char *serialno_bin,
size_t serialno_bin_len);
gpg_error_t card_reset (card_t card, ctrl_t ctrl, int send_reset);
gpg_error_t select_application (ctrl_t ctrl, const char *name, card_t *r_app,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len);
gpg_error_t select_additional_application (ctrl_t ctrl, const char *name);
gpg_error_t app_switch_current_card (ctrl_t ctrl,
const unsigned char *serialno,
size_t serialnolen);
gpg_error_t app_switch_active_app (card_t card, ctrl_t ctrl,
const char *appname);
char *get_supported_applications (void);
card_t card_ref (card_t card);
void card_unref (card_t card);
void card_unref_locked (card_t card);
gpg_error_t app_munge_serialno (card_t card);
gpg_error_t app_write_learn_status (card_t card, ctrl_t ctrl,
unsigned int flags);
gpg_error_t app_readcert (card_t card, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen);
gpg_error_t app_readkey (card_t card, ctrl_t ctrl,
const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen);
gpg_error_t app_getattr (card_t card, ctrl_t ctrl, const char *name);
gpg_error_t app_setattr (card_t card, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen);
gpg_error_t app_sign (card_t card, ctrl_t ctrl,
const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
gpg_error_t app_auth (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
gpg_error_t app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info);
gpg_error_t app_writecert (card_t card, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen);
gpg_error_t app_writekey (card_t card, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen);
gpg_error_t app_genkey (card_t card, ctrl_t ctrl,
const char *keynostr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t app_get_challenge (card_t card, ctrl_t ctrl, size_t nbytes,
unsigned char *buffer);
gpg_error_t app_change_pin (card_t card, ctrl_t ctrl,
const char *chvnostr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
gpg_error_t app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg);
card_t app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str,
int capability);
/*-- app-openpgp.c --*/
gpg_error_t app_select_openpgp (app_t app);
/*-- app-nks.c --*/
gpg_error_t app_select_nks (app_t app);
/*-- app-dinsig.c --*/
gpg_error_t app_select_dinsig (app_t app);
/*-- app-p15.c --*/
gpg_error_t app_select_p15 (app_t app);
/*-- app-geldkarte.c --*/
gpg_error_t app_select_geldkarte (app_t app);
/*-- app-sc-hsm.c --*/
gpg_error_t app_select_sc_hsm (app_t app);
/*-- app-piv.c --*/
gpg_error_t app_select_piv (app_t app);
#endif /*GNUPG_SCD_APP_COMMON_H*/
diff --git a/scd/app-nks.c b/scd/app-nks.c
index bdf065145..efc11296a 100644
--- a/scd/app-nks.c
+++ b/scd/app-nks.c
@@ -1,1479 +1,1479 @@
/* app-nks.c - The Telesec NKS card application.
* Copyright (C) 2004, 2007, 2008, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Notes:
- We are now targeting TCOS 3 cards and it may happen that there is
a regression towards TCOS 2 cards. Please report.
- The TKS3 AUT key is not used. It seems that it is only useful for
the internal authentication command and not accessible by other
applications. The key itself is in the encryption class but the
corresponding certificate has only the digitalSignature
capability.
- If required, we automagically switch between the NKS application
and the SigG application. This avoids to use the DINSIG
application which is somewhat limited, has no support for Secure
Messaging as required by TCOS 3 and has no way to change the PIN
or even set the NullPIN.
- We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer
cards. This is because the NKS application has moved to DF02 with
TCOS 3 and thus we better use a DF independent tag.
- We use only the global PINs for the NKS application.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "apdu.h"
#include "../common/host2net.h"
static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
static struct
{
int is_sigg; /* Valid for SigG application. */
int fid; /* File ID. */
int nks_ver; /* 0 for NKS version 2, 3 for version 3. */
int certtype; /* Type of certificate or 0 if it is not a certificate. */
int iskeypair; /* If true has the FID of the corresponding certificate. */
int issignkey; /* True if file is a key usable for signing. */
int isenckey; /* True if file is a key usable for decryption. */
unsigned char kid; /* Corresponding key references. */
} filelist[] = {
{ 0, 0x4531, 0, 0, 0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */
{ 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
{ 0, 0x4331, 0, 100 },
{ 0, 0x4332, 0, 100 },
{ 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
{ 0, 0x45B1, 0, 0, 0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */
{ 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
{ 0, 0x43B1, 0, 100 },
{ 0, 0x43B2, 0, 100 },
/* The authentication key is not used. */
/* { 0, 0x4571, 3, 0, 0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */
/* { 0, 0xC500, 3, 101 }, /\* EF_C.NKS.AUT *\/ */
{ 0, 0x45B2, 3, 0, 0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */
{ 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */
{ 1, 0x4531, 3, 0, 0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG */
{ 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */
{ 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */
{ 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */
{ 0, 0 }
};
/* Object with application (i.e. NKS) specific data. */
struct app_local_s {
int nks_version; /* NKS version. */
int sigg_active; /* True if switched to the SigG application. */
int sigg_msig_checked;/* True if we checked for a mass signature card. */
int sigg_is_msig; /* True if this is a mass signature card. */
int need_app_select; /* Need to re-select the application. */
};
static gpg_error_t switch_application (app_t app, int enable_sigg);
/* Release local data. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
xfree (app->app_local);
app->app_local = NULL;
}
}
static int
all_zero_p (void *buffer, size_t length)
{
char *p;
for (p=buffer; length; length--, p++)
if (*p)
return 0;
return 1;
}
/* Read the file with FID, assume it contains a public key and return
its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */
static gpg_error_t
keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
{
gpg_error_t err;
unsigned char grip[20];
unsigned char *buffer[2];
size_t buflen[2];
gcry_sexp_t sexp;
int i;
int offset[2] = { 0, 0 };
err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
return err;
err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
&buffer[0], &buflen[0]);
if (err)
return err;
err = iso7816_read_record (app_get_slot (app), 2, 1, 0,
&buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if (app->app_local->nks_version < 3)
{
/* Old versions of NKS store the values in a TLV encoded format.
We need to do some checks. */
for (i=0; i < 2; i++)
{
/* Check that the value appears like an integer encoded as
Simple-TLV. We don't check the tag because the tests cards I
have use 1 for both, the modulus and the exponent - the
example in the documentation gives 2 for the exponent. */
if (buflen[i] < 3)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (buffer[i][1] != buflen[i]-2 )
err = gpg_error (GPG_ERR_INV_OBJ);
else
offset[i] = 2;
}
}
else
{
/* Remove leading zeroes to get a correct keygrip. Take care of
negative numbers. We should also fix it the same way in
libgcrypt but we can't yet rely on it yet. */
for (i=0; i < 2; i++)
{
while (buflen[i]-offset[i] > 1
&& !buffer[i][offset[i]]
&& !(buffer[i][offset[i]+1] & 0x80))
offset[i]++;
}
}
/* Check whether negative values are not prefixed with a zero and
fix that. */
for (i=0; i < 2; i++)
{
if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80))
{
unsigned char *newbuf;
size_t newlen;
newlen = 1 + buflen[i] - offset[i];
newbuf = xtrymalloc (newlen);
if (!newlen)
{
xfree (buffer[0]);
xfree (buffer[1]);
return gpg_error_from_syserror ();
}
newbuf[0] = 0;
memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]);
xfree (buffer[i]);
buffer[i] = newbuf;
buflen[i] = newlen;
offset[i] = 0;
}
}
if (!err)
err = gcry_sexp_build (&sexp, NULL,
"(public-key (rsa (n %b) (e %b)))",
(int)buflen[0]-offset[0], buffer[0]+offset[0],
(int)buflen[1]-offset[1], buffer[1]+offset[1]);
xfree (buffer[0]);
xfree (buffer[1]);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
{
err = gpg_error (GPG_ERR_INTERNAL); /* i.e. RSA not supported by
libgcrypt. */
}
else
{
bin2hex (grip, 20, r_gripstr);
}
gcry_sexp_release (sexp);
return err;
}
/* TCOS responds to a verify with empty data (i.e. without the Lc
byte) with the status of the PIN. PWID is the PIN ID, If SIGG is
true, the application is switched into SigG mode.
Returns:
-1 = Error retrieving the data,
-2 = No such PIN,
-3 = PIN blocked,
- -4 = NullPIN activ,
+ -4 = NullPIN active,
n >= 0 = Number of verification attempts left. */
static int
get_chv_status (app_t app, int sigg, int pwid)
{
unsigned char *result = NULL;
size_t resultlen;
char command[4];
int rc;
if (switch_application (app, sigg))
return sigg? -2 : -1; /* No such PIN / General error. */
command[0] = 0x00;
command[1] = 0x20;
command[2] = 0x00;
command[3] = pwid;
if (apdu_send_direct (app_get_slot (app), 0, (unsigned char *)command,
4, 0, NULL, &result, &resultlen))
rc = -1; /* Error. */
else if (resultlen < 2)
rc = -1; /* Error. */
else
{
unsigned int sw = buf16_to_uint (result+resultlen-2);
if (sw == 0x6a88)
rc = -2; /* No such PIN. */
else if (sw == 0x6983)
rc = -3; /* PIN is blocked. */
else if (sw == 0x6985)
- rc = -4; /* NullPIN is activ. */
+ rc = -4; /* NullPIN is active. */
else if ((sw & 0xfff0) == 0x63C0)
rc = (sw & 0x000f); /* PIN has N tries left. */
else
rc = -1; /* Other error. */
}
xfree (result);
return rc;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int special;
} table[] = {
{ "$AUTHKEYID", 1 },
{ "$ENCRKEYID", 2 },
{ "$SIGNKEYID", 3 },
{ "NKS-VERSION", 4 },
{ "CHV-STATUS", 5 },
{ NULL, 0 }
};
gpg_error_t err = 0;
int idx;
char buffer[100];
err = switch_application (app, 0);
if (err)
return err;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
switch (table[idx].special)
{
case 1: /* $AUTHKEYID */
{
/* NetKey 3.0 cards define an authentication key but according
to the specs this key is only usable for encryption and not
signing. it might work anyway but it has not yet been
tested - fixme. Thus for now we use the NKS signature key
for authentication. */
char const tmp[] = "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 2: /* $ENCRKEYID */
{
char const tmp[] = "NKS-NKS3.45B1";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 3: /* $SIGNKEYID */
{
char const tmp[] = "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 4: /* NKS-VERSION */
snprintf (buffer, sizeof buffer, "%d", app->app_local->nks_version);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
break;
case 5: /* CHV-STATUS */
{
/* Returns: PW1.CH PW2.CH PW1.CH.SIG PW2.CH.SIG That are the
two global passwords followed by the two SigG passwords.
For the values, see the function get_chv_status. */
int tmp[4];
/* We use a helper array so that we can control that there is
no superfluous application switch. Note that PW2.CH.SIG
really has the identifier 0x83 and not 0x82 as one would
expect. */
tmp[0] = get_chv_status (app, 0, 0x00);
tmp[1] = get_chv_status (app, 0, 0x01);
tmp[2] = get_chv_status (app, 1, 0x81);
tmp[3] = get_chv_status (app, 1, 0x83);
snprintf (buffer, sizeof buffer,
"%d %d %d %d", tmp[0], tmp[1], tmp[2], tmp[3]);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
}
break;
default:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
return err;
}
static void
do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags, int is_sigg)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
int i;
const char *tag;
const char *usage;
if (is_sigg)
tag = "SIGG";
else if (app->app_local->nks_version < 3)
tag = "DF01";
else
tag = "NKS3";
/* Output information about all useful objects in the NKS application. */
for (i=0; filelist[i].fid; i++)
{
if (filelist[i].nks_ver > app->app_local->nks_version)
continue;
if (!!filelist[i].is_sigg != !!is_sigg)
continue;
if (filelist[i].certtype && !(flags & APP_LEARN_FLAG_KEYPAIRINFO))
{
size_t len;
len = app_help_read_length_of_cert (app_get_slot (app),
filelist[i].fid, NULL);
if (len)
{
/* FIXME: We should store the length in the application's
context so that a following readcert does only need to
read that many bytes. */
snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype);
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
else if (filelist[i].iskeypair)
{
char gripstr[40+1];
err = keygripstr_from_pk_file (app, filelist[i].fid, gripstr);
if (err)
log_error ("can't get keygrip from FID 0x%04X: %s\n",
filelist[i].fid, gpg_strerror (err));
else
{
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
if (filelist[i].issignkey && filelist[i].isenckey)
usage = "sae";
else if (filelist[i].issignkey)
usage = "sa";
else if (filelist[i].isenckey)
usage = "e";
else
usage = "";
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
id_buf, strlen (id_buf),
usage, strlen (usage),
NULL, (size_t)0);
}
}
}
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
err = switch_application (app, 0);
if (err)
return err;
do_learn_status_core (app, ctrl, flags, 0);
err = switch_application (app, 1);
if (err)
return 0; /* Silently ignore if we can't switch to SigG. */
do_learn_status_core (app, ctrl, flags, 1);
return 0;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int i, fid;
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
int is_sigg = 0;
*cert = NULL;
*certlen = 0;
if (!strncmp (certid, "NKS-NKS3.", 9))
;
else if (!strncmp (certid, "NKS-DF01.", 9))
;
else if (!strncmp (certid, "NKS-SIGG.", 9))
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
certid += 9;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
for (i=0; filelist[i].fid; i++)
if ((filelist[i].certtype || filelist[i].iskeypair)
&& filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
/* If the requested objects is a plain public key, redirect it to
the corresponding certificate. The whole system is a bit messy
because we sometime use the key directly or let the caller
retrieve the key from the certificate. The rationale for
that is to support not-yet stored certificates. */
if (filelist[i].iskeypair)
fid = filelist[i].iskeypair;
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
{
log_error ("error selecting FID 0x%04X: %s\n", fid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff)
{
log_info ("no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/* Handle the READKEY command. On success a canonical encoded
S-expression with the public key will get stored at PK and its
length at PKLEN; the caller must release that buffer. On error PK
and PKLEN are not changed and an error code is returned. As of now
this function is only useful for the internal authentication key.
Other keys are automagically retrieved via by means of the
certificate parsing code in commands.c:cmd_readkey. For internal
use PK and PKLEN may be NULL to just check for an existing key. */
static gpg_error_t
do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
unsigned char *buffer[2];
size_t buflen[2];
unsigned short path[1] = { 0x4500 };
/* We use a generic name to retrieve PK.AUT.IFD-SPK. */
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else /* Return the error code expected by cmd_readkey. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* Access the KEYD file which is always in the master directory. */
err = iso7816_select_path (app_get_slot (app), path, DIM (path));
if (err)
return err;
/* Due to the above select we need to re-select our application. */
app->app_local->need_app_select = 1;
/* Get the two records. */
err = iso7816_read_record (app_get_slot (app), 5, 1, 0,
&buffer[0], &buflen[0]);
if (err)
return err;
if (all_zero_p (buffer[0], buflen[0]))
{
xfree (buffer[0]);
return gpg_error (GPG_ERR_NOT_FOUND);
}
err = iso7816_read_record (app_get_slot (app), 6, 1, 0,
&buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if ((flags & APP_READKEY_FLAG_INFO))
{
/* Not yet implemented but we won't get here for any regular
* keyrefs anyway, thus the top layer will provide the
* keypairinfo from the certificate. */
(void)ctrl;
}
if (pk && pklen)
{
*pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0],
buffer[1], buflen[1],
pklen);
if (!*pk)
err = gpg_error_from_syserror ();
}
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
/* Handle the WRITEKEY command for NKS. This function expects a
canonical encoded S-expression with the public key in KEYDATA and
its length in KEYDATALEN. The only supported KEYID is
"$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of
FLAGS indicates whether an existing key shall get overwritten.
PINCB and PINCB_ARG are the usual arguments for the pinentry
callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
unsigned int nbits;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
;
else
return gpg_error (GPG_ERR_INV_ID);
if (!force && !do_readkey (app, ctrl, keyid, 0, NULL, NULL))
return gpg_error (GPG_ERR_EEXIST);
/* Parse the S-expression. */
err = get_rsa_pk_from_canon_sexp (keydata, keydatalen,
&rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len);
if (err)
goto leave;
/* Check that the parameters match the requirements. */
nbits = app_help_count_bits (rsa_n, rsa_n_len);
if (nbits != 1024)
{
log_error (_("RSA modulus missing or not of size %d bits\n"), 1024);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
nbits = app_help_count_bits (rsa_e, rsa_e_len);
if (nbits < 2 || nbits > 32)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
32);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* /\* Store them. *\/ */
/* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */
/* if (err) */
/* goto leave; */
/* Send the MSE:Store_Public_Key. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
/* mse = xtrymalloc (1000); */
/* mse[0] = 0x80; /\* Algorithm reference. *\/ */
/* mse[1] = 1; */
/* mse[2] = 0x17; */
/* mse[3] = 0x84; /\* Private key reference. *\/ */
/* mse[4] = 1; */
/* mse[5] = 0x77; */
/* mse[6] = 0x7F; /\* Public key parameter. *\/ */
/* mse[7] = 0x49; */
/* mse[8] = 0x81; */
/* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */
/* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */
/* mse[11] = 0x81; */
/* mse[12] = rsa_n_len; */
/* memcpy (mse+12, rsa_n, rsa_n_len); */
/* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */
/* mse[12] = rsa_e_len; */
/* memcpy (mse+12, rsa_e, rsa_e_len); */
/* err = iso7816_manage_security_env (app_get_slot (app), 0x81, 0xB6, */
/* mse, sizeof mse); */
leave:
return err;
}
static gpg_error_t
basic_pin_checks (const char *pinvalue, int minlen, int maxlen)
{
if (strlen (pinvalue) < minlen)
{
log_error ("PIN is too short; minimum length is %d\n", minlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) > maxlen)
{
log_error ("PIN is too large; maximum length is %d\n", maxlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
return 0;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app, int pwid, const char *desc,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
pininfo_t pininfo;
int rc;
if (!desc)
desc = "PIN";
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = 6;
pininfo.maxlen = 16;
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
{
rc = pincb (pincb_arg, desc, NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app_get_slot (app), pwid, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
char *pinvalue;
rc = pincb (pincb_arg, desc, &pinvalue);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen);
if (rc)
{
xfree (pinvalue);
return rc;
}
rc = iso7816_verify (app_get_slot (app), pwid,
pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
{
if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS )
log_error (_("the NullPIN has not yet been changed\n"));
else
log_error ("verify PIN failed\n");
return rc;
}
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
int rc, i;
int is_sigg = 0;
int fid;
unsigned char kid;
unsigned char data[83]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix. */
size_t datalen;
(void)ctrl;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
switch (indatalen)
{
case 16: case 20: case 35: case 47: case 51: case 67: case 83: break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (is_sigg && app->app_local->sigg_is_msig)
{
log_info ("mass signature cards are not allowed\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].issignkey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
/* Prepare the DER object from INDATA. */
if (app->app_local->nks_version > 2 && (indatalen == 35
|| indatalen == 47
|| indatalen == 51
|| indatalen == 67
|| indatalen == 83))
{
/* The caller send data matching the length of the ASN.1 encoded
hash for SHA-{1,224,256,384,512}. Assume that is okay. */
assert (indatalen <= sizeof data);
memcpy (data, indata, indatalen);
datalen = indatalen;
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. This is for TCOS 2. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
datalen = 35;
}
else if (indatalen == 20)
{
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
datalen = 35;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
/* Send an MSE for PSO:Computer_Signature. */
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 2; /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check. */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
rc = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6,
mse, sizeof mse);
}
/* Verify using PW1.CH. */
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Compute the signature. */
if (!rc)
rc = iso7816_compute_ds (app_get_slot (app), 0, data, datalen, 0,
outdata, outdatalen);
return rc;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
int rc, i;
int is_sigg = 0;
int fid;
int kid;
(void)ctrl;
(void)r_info;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check that the provided ID is valid. This is not really needed
but we do it to enforce correct usage by the caller. */
if (!strncmp (keyidstr, "NKS-NKS3.", 9) )
;
else if (!strncmp (keyidstr, "NKS-DF01.", 9) )
;
else if (!strncmp (keyidstr, "NKS-SIGG.", 9) )
is_sigg = 1;
else
return gpg_error (GPG_ERR_INV_ID);
keyidstr += 9;
rc = switch_application (app, is_sigg);
if (rc)
return rc;
if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
|| !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3)
|| keyidstr[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyidstr);
for (i=0; filelist[i].fid; i++)
if (filelist[i].iskeypair && filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!filelist[i].isenckey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[i].kid;
if (app->app_local->nks_version > 2)
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
rc = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, sizeof mse);
}
else
{
static const unsigned char mse[] =
{
0x80, 1, 0x10, /* Select algorithm RSA. */
0x84, 1, 0x81 /* Select local secret key 1 for decryption. */
};
rc = iso7816_manage_security_env (app_get_slot (app), 0xC1, 0xB8,
mse, sizeof mse);
}
if (!rc)
rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
/* Note that we need to use extended length APDUs for TCOS 3 cards.
Command chaining does not work. */
if (!rc)
rc = iso7816_decipher (app_get_slot (app),
app->app_local->nks_version > 2? 1:0,
indata, indatalen, 0, 0x81,
outdata, outdatalen);
return rc;
}
/* Parse a password ID string. Returns NULL on error or a string
suitable as passphrase prompt on success. On success stores the
reference value for the password at R_PWID and a flag indicating
that the SigG application is to be used at R_SIGG. If NEW_MODE is
true, the returned description is suitable for a new Password.
Supported values for PWIDSTR are:
PW1.CH - Global password 1
PW2.CH - Global password 2
PW1.CH.SIG - SigG password 1
PW2.CH.SIG - SigG password 2
*/
static const char *
parse_pwidstr (const char *pwidstr, int new_mode, int *r_sigg, int *r_pwid)
{
const char *desc;
if (!pwidstr)
desc = NULL;
else if (!strcmp (pwidstr, "PW1.CH"))
{
*r_sigg = 0;
*r_pwid = 0x00;
/* TRANSLATORS: Do not translate the "|*|" prefixes but keep
them verbatim at the start of the string. */
desc = (new_mode
? _("|N|Please enter a new PIN for the standard keys.")
: _("||Please enter the PIN for the standard keys."));
}
else if (!strcmp (pwidstr, "PW2.CH"))
{
*r_pwid = 0x01;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the standard keys.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the standard keys."));
}
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
{
*r_pwid = 0x81;
*r_sigg = 1;
desc = (new_mode
? _("|N|Please enter a new PIN for the key to create "
"qualified signatures.")
: _("||Please enter the PIN for the key to create "
"qualified signatures."));
}
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
{
*r_pwid = 0x83; /* Yes, that is 83 and not 82. */
*r_sigg = 1;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the key to create qualified signatures.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the key to create qualified signatures."));
}
else
{
*r_pwid = 0; /* Only to avoid gcc warning in calling function. */
desc = NULL; /* Error. */
}
return desc;
}
/* Handle the PASSWD command. See parse_pwidstr() for allowed values
for CHVNOSTR. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *newpin = NULL;
char *oldpin = NULL;
size_t newpinlen;
size_t oldpinlen;
int is_sigg;
const char *newdesc;
int pwid;
pininfo_t pininfo;
(void)ctrl;
/* The minimum length is enforced by TCOS, the maximum length is
just a reasonable value. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.minlen = 6;
pininfo.maxlen = 16;
newdesc = parse_pwidstr (pwidstr, 1, &is_sigg, &pwid);
if (!newdesc)
return gpg_error (GPG_ERR_INV_ID);
if ((flags & APP_CHANGE_FLAG_CLEAR))
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = switch_application (app, is_sigg);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would
fail if the Nullpin is still set. */
oldpin = xtrycalloc (1, 6);
if (!oldpin)
{
err = gpg_error_from_syserror ();
goto leave;
}
oldpinlen = 6;
}
else
{
const char *desc;
int dummy1, dummy2;
if ((flags & APP_CHANGE_FLAG_RESET))
{
/* Reset mode: Ask for the alternate PIN. */
const char *altpwidstr;
if (!strcmp (pwidstr, "PW1.CH"))
altpwidstr = "PW2.CH";
else if (!strcmp (pwidstr, "PW2.CH"))
altpwidstr = "PW1.CH";
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
altpwidstr = "PW2.CH.SIG";
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
altpwidstr = "PW1.CH.SIG";
else
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
desc = parse_pwidstr (altpwidstr, 0, &dummy1, &dummy2);
}
else
{
/* Regular change mode: Ask for the old PIN. */
desc = parse_pwidstr (pwidstr, 0, &dummy1, &dummy2);
}
err = pincb (pincb_arg, desc, &oldpin);
if (err)
{
log_error ("error getting old PIN: %s\n", gpg_strerror (err));
goto leave;
}
oldpinlen = strlen (oldpin);
err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
}
err = pincb (pincb_arg, newdesc, &newpin);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
goto leave;
}
newpinlen = strlen (newpin);
err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *data;
size_t datalen = oldpinlen + newpinlen;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (data, oldpin, oldpinlen);
memcpy (data+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app_get_slot (app), pwid,
data, datalen);
wipememory (data, datalen);
xfree (data);
}
else
err = iso7816_change_reference_data (app_get_slot (app), pwid,
oldpin, oldpinlen,
newpin, newpinlen);
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
static gpg_error_t
do_check_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int pwid;
int is_sigg;
const char *desc;
(void)ctrl;
desc = parse_pwidstr (pwidstr, 0, &is_sigg, &pwid);
if (!desc)
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, is_sigg);
if (err)
return err;
return verify_pin (app, pwid, desc, pincb, pincb_arg);
}
/* Return the version of the NKS application. */
static int
get_nks_version (int slot)
{
unsigned char *result = NULL;
size_t resultlen;
int type;
if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0,
NULL, &result, &resultlen))
return 2; /* NKS 2 does not support this command. */
/* Example value: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00
vv tt ccccccccccccccccc aa bb cc vvvvvvvvvvv xx
vendor (Philips) -+ | | | | | | |
chip type -----------+ | | | | | |
chip id ----------------+ | | | | |
card type (3 - tcos 3) -------------------+ | | | |
OS version of card type ---------------------+ | | |
OS release of card type ------------------------+ | |
OS vendor internal version ------------------------+ |
RFU -----------------------------------------------------------+
*/
if (resultlen < 16)
type = 0; /* Invalid data returned. */
else
type = result[8];
xfree (result);
return type;
}
/* If ENABLE_SIGG is true switch to the SigG application if not yet
active. If false switch to the NKS application if not yet active.
Returns 0 on success. */
static gpg_error_t
switch_application (app_t app, int enable_sigg)
{
gpg_error_t err;
if (((app->app_local->sigg_active && enable_sigg)
|| (!app->app_local->sigg_active && !enable_sigg))
&& !app->app_local->need_app_select)
return 0; /* Already switched. */
log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS");
if (enable_sigg)
err = iso7816_select_application (app_get_slot (app),
aid_sigg, sizeof aid_sigg, 0);
else
err = iso7816_select_application (app_get_slot (app),
aid_nks, sizeof aid_nks, 0);
if (!err && enable_sigg && app->app_local->nks_version >= 3
&& !app->app_local->sigg_msig_checked)
{
/* Check whether this card is a mass signature card. */
unsigned char *buffer;
size_t buflen;
const unsigned char *tmpl;
size_t tmpllen;
app->app_local->sigg_msig_checked = 1;
app->app_local->sigg_is_msig = 1;
err = iso7816_select_file (app_get_slot (app), 0x5349, 0);
if (!err)
err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
&buffer, &buflen);
if (!err)
{
tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen);
if (tmpl && tmpllen == 12
&& !memcmp (tmpl,
"\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83",
12))
app->app_local->sigg_is_msig = 0;
xfree (buffer);
}
if (app->app_local->sigg_is_msig)
log_info ("This is a mass signature card\n");
}
if (!err)
{
app->app_local->need_app_select = 0;
app->app_local->sigg_active = enable_sigg;
}
else
log_error ("app-nks: error switching to %s: %s\n",
enable_sigg? "SigG":"NKS", gpg_strerror (err));
return err;
}
/* Select the NKS application. */
gpg_error_t
app_select_nks (app_t app)
{
int slot = app_get_slot (app);
int rc;
rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0);
if (!rc)
{
app->apptype = APPTYPE_NKS;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->app_local->nks_version = get_nks_version (slot);
if (opt.verbose)
log_info ("Detected NKS version: %d\n", app->app_local->nks_version);
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = NULL;
app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.writekey = do_writekey;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-p15.c b/scd/app-p15.c
index 86902e90b..016f5c3f3 100644
--- a/scd/app-p15.c
+++ b/scd/app-p15.c
@@ -1,3439 +1,3439 @@
/* app-p15.c - The pkcs#15 card application.
* Copyright (C) 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Information pertaining to the BELPIC developer card samples:
Unblock PUK: "222222111111"
Reset PIN: "333333111111")
e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF
and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF
should change the PIN into 1234.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "apdu.h" /* fixme: we should move the card detection to a
separate file */
/* Types of cards we know and which needs special treatment. */
typedef enum
{
CARD_TYPE_UNKNOWN,
CARD_TYPE_TCOS,
CARD_TYPE_MICARDO,
CARD_TYPE_BELPIC /* Belgian eID card specs. */
}
card_type_t;
/* A list card types with ATRs noticed with these cards. */
#define X(a) ((unsigned char const *)(a))
static struct
{
size_t atrlen;
unsigned char const *atr;
card_type_t type;
} card_atr_list[] = {
{ 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80"
"\x90\x00\x8B"),
CARD_TYPE_TCOS }, /* SLE44 */
{ 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80"
"\x90\x00\x91"),
CARD_TYPE_TCOS }, /* SLE66S */
{ 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80"
"\x90\x00\x66"),
CARD_TYPE_TCOS }, /* SLE66P */
{ 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00"
"\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"),
CARD_TYPE_MICARDO }, /* German BMI card */
{ 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80"
"\x00\x90\x00"),
CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */
{ 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49"
"\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"),
CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */
{ 0 }
};
#undef X
/* The AID of PKCS15. */
static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The Belgian eID variant - they didn't understood why a shared AID
is useful for a standard. Oh well. */
static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The PIN types as defined in pkcs#15 v1.1 */
typedef enum
{
PIN_TYPE_BCD = 0,
PIN_TYPE_ASCII_NUMERIC = 1,
PIN_TYPE_UTF8 = 2,
PIN_TYPE_HALF_NIBBLE_BCD = 3,
PIN_TYPE_ISO9564_1 = 4
} pin_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* To avoid reading a certificate more than once, we cache it in an
allocated memory IMAGE of IMAGELEN. */
size_t imagelen;
unsigned char *image;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the CDF and the path itself.
path[0] is the top DF (usually 0x3f00). The path will never be
empty. */
size_t pathlen;
unsigned short path[1];
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The keyReference and a flag telling whether it is valid. */
unsigned long key_reference;
int key_reference_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the PrKDF and the path itself.
path[0] is the top DF (usually 0x3f00). */
size_t pathlen;
unsigned short path[1];
};
typedef struct prkdf_object_s *prkdf_object_t;
/* This is an object to store information about a Authentication
Object Directory File (AODF) in a format suitable for further
processing by us. To keep memory management, simple we use a linked
list of items; i.e. one such object represents one authentication
object and the list the entire AOKDF. */
struct aodf_object_s
{
/* Link to next item when used in a linked list. */
struct aodf_object_s *next;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* The PIN Flags. */
struct
{
unsigned int case_sensitive: 1;
unsigned int local: 1;
unsigned int change_disabled: 1;
unsigned int unblock_disabled: 1;
unsigned int initialized: 1;
unsigned int needs_padding: 1;
unsigned int unblocking_pin: 1;
unsigned int so_pin: 1;
unsigned int disable_allowed: 1;
unsigned int integrity_protected: 1;
unsigned int confidentiality_protected: 1;
unsigned int exchange_ref_data: 1;
} pinflags;
/* The PIN Type. */
pin_type_t pintype;
/* The minimum length of a PIN. */
unsigned long min_length;
/* The stored length of a PIN. */
unsigned long stored_length;
/* The maximum length of a PIN and a flag telling whether it is valid. */
unsigned long max_length;
int max_length_valid;
/* The pinReference and a flag telling whether it is valid. */
unsigned long pin_reference;
int pin_reference_valid;
/* The padChar and a flag telling whether it is valid. */
char pad_char;
int pad_char_valid;
/* Set to true if a length and offset is available. */
int have_off;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the Aodf and the path itself.
path[0] is the top DF (usually 0x3f00). PATH is optional and thus
may be NULL. Malloced.*/
size_t pathlen;
unsigned short *path;
};
typedef struct aodf_object_s *aodf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* The home DF. Note, that we don't yet support a multilevel
hierarchy. Thus we assume this is directly below the MF. */
unsigned short home_df;
/* The type of the card. */
card_type_t card_type;
/* Flag indicating whether we may use direct path selection. */
int direct_path_selection;
/* Structure with the EFIDs of the objects described in the ODF
file. */
struct
{
unsigned short private_keys;
unsigned short public_keys;
unsigned short trusted_public_keys;
unsigned short secret_keys;
unsigned short certificates;
unsigned short trusted_certificates;
unsigned short useful_certificates;
unsigned short data_objects;
unsigned short auth_objects;
} odf;
/* The PKCS#15 serialnumber from EF(TokeiNFo) or NULL. Malloced. */
unsigned char *serialno;
size_t serialnolen;
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all useful certificates. */
cdf_object_t useful_certificate_info;
/* Information on all private keys. */
prkdf_object_t private_key_info;
/* Information on all authentication objects. */
aodf_object_t auth_object_info;
};
/*** Local prototypes. ***/
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
xfree (a->image);
xfree (a->objid);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->objid);
xfree (a->authid);
xfree (a);
a = tmp;
}
}
/* Release just one aodf object. */
void
release_aodf_object (aodf_object_t a)
{
if (a)
{
xfree (a->objid);
xfree (a->authid);
xfree (a->path);
xfree (a);
}
}
/* Release the AODF list A. */
static void
release_aodflist (aodf_object_t a)
{
while (a)
{
aodf_object_t tmp = a->next;
release_aodf_object (a);
a = tmp;
}
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_cdflist (app->app_local->certificate_info);
release_cdflist (app->app_local->trusted_certificate_info);
release_cdflist (app->app_local->useful_certificate_info);
release_prkdflist (app->app_local->private_key_info);
release_aodflist (app->app_local->auth_object_info);
xfree (app->app_local->serialno);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
desctription of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. */
static gpg_error_t
select_and_read_binary (int slot, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen)
{
gpg_error_t err;
err = iso7816_select_file (slot, efid, 0);
if (err)
{
log_error ("error selecting %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (slot, 0, 0, buffer, buflen);
if (err)
{
log_error ("error reading %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
return 0;
}
/* This function calls select file to read a file using a complete
path which may or may not start at the master file (MF). */
static gpg_error_t
select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen)
{
gpg_error_t err;
int i, j;
if (!pathlen)
return gpg_error (GPG_ERR_INV_VALUE);
if (pathlen && *path != 0x3f00 )
log_debug ("WARNING: relative path selection not yet implemented\n");
if (app->app_local->direct_path_selection)
{
err = iso7816_select_path (app_get_slot (app), path+1, pathlen-1);
if (err)
{
log_error ("error selecting path ");
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
else
{
/* FIXME: Need code to remember the last PATH so that we can decide
what select commands to send in case the path does not start off
with 3F00. We might also want to use direct path selection if
supported by the card. */
for (i=0; i < pathlen; i++)
{
err = iso7816_select_file (app_get_slot (app),
path[i], !(i+1 == pathlen));
if (err)
{
log_error ("error selecting part %d from path ", i);
for (j=0; j < pathlen; j++)
log_printf ("%04hX", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
}
}
return 0;
}
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (app_t app, const char *certid,
unsigned char **r_objid, size_t *r_objidlen)
{
char tmpbuf[10];
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (app->app_local->home_df)
snprintf (tmpbuf, sizeof tmpbuf,
"P15-%04X.", (unsigned int)(app->app_local->home_df & 0xffff));
else
strcpy (tmpbuf, "P15.");
if (strncmp (certid, tmpbuf, strlen (tmpbuf)) )
{
if (!strncmp (certid, "P15.", 4)
|| (!strncmp (certid, "P15-", 4)
&& hexdigitp (certid+4)
&& hexdigitp (certid+5)
&& hexdigitp (certid+6)
&& hexdigitp (certid+7)
&& certid[8] == '.'))
return gpg_error (GPG_ERR_NOT_FOUND);
return gpg_error (GPG_ERR_INV_ID);
}
certid += strlen (tmpbuf);
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
err = parse_certid (app, certid, &objid, &objidlen);
if (err)
return err;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
xfree (objid);
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (app, keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Read and parse the Object Directory File and store away the
pointers. ODF_FID shall contain the FID of the ODF.
Example of such a file:
A0 06 30 04 04 02 60 34 = Private Keys
A4 06 30 04 04 02 60 35 = Certificates
A5 06 30 04 04 02 60 36 = TrustedCertificates
A7 06 30 04 04 02 60 37 = DataObjects
A8 06 30 04 04 02 60 38 = AuthObjects
These are all PathOrObjects using the path CHOICE element. The
paths are octet strings of length 2. Using this Path CHOICE
element is recommended, so we only implement that for now.
*/
static gpg_error_t
read_ef_odf (app_t app, unsigned short odf_fid)
{
gpg_error_t err;
unsigned char *buffer, *p;
size_t buflen;
unsigned short value;
size_t offset;
err = select_and_read_binary (app_get_slot (app), odf_fid, "ODF",
&buffer, &buflen);
if (err)
return err;
if (buflen < 8)
{
log_error ("error: ODF too short\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
p = buffer;
while (buflen && *p && *p != 0xff)
{
if ( buflen >= 8
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) )
{
offset = 6;
}
else if ( buflen >= 12
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7)
&& app->app_local->home_df == ((p[8]<<8)|p[9]) )
{
/* We only allow a full path if all files are at the same
level and below the home directory. The extend this we
would need to make use of new data type capable of
keeping a full path. */
offset = 10;
}
else
{
log_error ("ODF format is not supported by us\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
switch ((p[0] & 0x0f))
{
case 0: value = app->app_local->odf.private_keys; break;
case 1: value = app->app_local->odf.public_keys; break;
case 2: value = app->app_local->odf.trusted_public_keys; break;
case 3: value = app->app_local->odf.secret_keys; break;
case 4: value = app->app_local->odf.certificates; break;
case 5: value = app->app_local->odf.trusted_certificates; break;
case 6: value = app->app_local->odf.useful_certificates; break;
case 7: value = app->app_local->odf.data_objects; break;
case 8: value = app->app_local->odf.auth_objects; break;
default: value = 0; break;
}
if (value)
{
log_error ("duplicate object type %d in ODF ignored\n",(p[0]&0x0f));
continue;
}
value = ((p[offset] << 8) | p[offset+1]);
switch ((p[0] & 0x0f))
{
case 0: app->app_local->odf.private_keys = value; break;
case 1: app->app_local->odf.public_keys = value; break;
case 2: app->app_local->odf.trusted_public_keys = value; break;
case 3: app->app_local->odf.secret_keys = value; break;
case 4: app->app_local->odf.certificates = value; break;
case 5: app->app_local->odf.trusted_certificates = value; break;
case 6: app->app_local->odf.useful_certificates = value; break;
case 7: app->app_local->odf.data_objects = value; break;
case 8: app->app_local->odf.auth_objects = value; break;
default:
log_error ("unknown object type %d in ODF ignored\n", (p[0]&0x0f));
}
offset += 2;
if (buflen < offset)
break;
p += offset;
buflen -= offset;
}
if (buflen)
log_info ("warning: %u bytes of garbage detected at end of ODF\n",
(unsigned int)buflen);
xfree (buffer);
return 0;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
/* Read and parse the Private Key Directory Files. */
/*
6034 (privatekeys)
30 33 30 11 0C 08 53 4B 2E 43 48 2E 44 53 03 02 030...SK.CH.DS..
06 80 04 01 07 30 0C 04 01 01 03 03 06 00 40 02 .....0........@.
02 00 50 A1 10 30 0E 30 08 04 06 3F 00 40 16 00 ..P..0.0...?.@..
50 02 02 04 00 30 33 30 11 0C 08 53 4B 2E 43 48 P....030...SK.CH
2E 4B 45 03 02 06 80 04 01 0A 30 0C 04 01 0C 03 .KE.......0.....
03 06 44 00 02 02 00 52 A1 10 30 0E 30 08 04 06 ..D....R..0.0...
3F 00 40 16 00 52 02 02 04 00 30 34 30 12 0C 09 ?.@..R....040...
53 4B 2E 43 48 2E 41 55 54 03 02 06 80 04 01 0A SK.CH.AUT.......
30 0C 04 01 0D 03 03 06 20 00 02 02 00 51 A1 10 0....... ....Q..
30 0E 30 08 04 06 3F 00 40 16 00 51 02 02 04 00 0.0...?.@..Q....
30 37 30 15 0C 0C 53 4B 2E 43 48 2E 44 53 2D 53 070...SK.CH.DS-S
50 58 03 02 06 80 04 01 0A 30 0C 04 01 02 03 03 PX.......0......
06 20 00 02 02 00 53 A1 10 30 0E 30 08 04 06 3F . ....S..0.0...?
00 40 16 00 53 02 02 04 00 00 00 00 00 00 00 00 .@..S...........
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 30 51: SEQUENCE {
2 30 17: SEQUENCE { -- commonObjectAttributes
4 0C 8: UTF8String 'SK.CH.DS'
14 03 2: BIT STRING 6 unused bits
: '01'B (bit 0)
18 04 1: OCTET STRING --authid
: 07
: }
21 30 12: SEQUENCE { -- commonKeyAttributes
23 04 1: OCTET STRING
: 01
26 03 3: BIT STRING 6 unused bits
: '1000000000'B (bit 9)
31 02 2: INTEGER 80 -- keyReference (optional)
: }
35 A1 16: [1] { -- keyAttributes
37 30 14: SEQUENCE { -- privateRSAKeyAttributes
39 30 8: SEQUENCE { -- objectValue
41 04 6: OCTET STRING --path
: 3F 00 40 16 00 50
: }
49 02 2: INTEGER 1024 -- modulus
: }
: }
: }
*/
static gpg_error_t
read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
prkdf_object_t prkdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */
err = select_and_read_binary (app_get_slot (app), fid, "PrKDF",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_cdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
const unsigned char *authid = NULL;
size_t authidlen = 0;
keyusage_flags_t usageflags;
unsigned long key_reference = 0;
int key_reference_valid = 0;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
authid = ppp;
authidlen = objlen;
}
no_authid:
;
}
/* Parse the commonKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
err = parse_keyusage_flags (ppp, objlen, &usageflags);
if (err)
goto parse_error;
ppp += objlen;
nnn -= objlen;
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* Skip the accessFlags. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto leave_cki;
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* Yep, this is the keyReference. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
key_reference = ul;
key_reference_valid = 1;
}
leave_cki:
;
}
/* Skip subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* RSA */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "EC key objects are not supported"; break;
case 1: errstr = "DH key objects are not supported"; break;
case 2: errstr = "DSA key objects are not supported"; break;
case 3: errstr = "KEA key objects are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, (sizeof *prkdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->objidlen = objidlen;
prkdf->objid = xtrymalloc (objidlen);
if (!prkdf->objid)
{
err = gpg_error_from_syserror ();
xfree (prkdf);
goto leave;
}
memcpy (prkdf->objid, objid, objidlen);
if (authid)
{
prkdf->authidlen = authidlen;
prkdf->authid = xtrymalloc (authidlen);
if (!prkdf->authid)
{
err = gpg_error_from_syserror ();
xfree (prkdf->objid);
xfree (prkdf);
goto leave;
}
memcpy (prkdf->authid, authid, authidlen);
}
prkdf->pathlen = objlen/2;
for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2)
prkdf->path[i] = ((pp[0] << 8) | pp[1]);
prkdf->usageflags = usageflags;
prkdf->key_reference = key_reference;
prkdf->key_reference_valid = key_reference_valid;
if (nn)
{
/* An index and length follows. */
prkdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->len = ul;
}
log_debug ("PrKDF %04hX: id=", fid);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
log_printf (" path=");
for (i=0; i < prkdf->pathlen; i++)
log_printf ("%04hX", prkdf->path[i]);
if (prkdf->have_off)
log_printf ("[%lu/%lu]", prkdf->off, prkdf->len);
if (prkdf->authid)
{
log_printf (" authid=");
for (i=0; i < prkdf->authidlen; i++)
log_printf ("%02X", prkdf->authid[i]);
}
if (prkdf->key_reference_valid)
log_printf (" keyref=0x%02lX", prkdf->key_reference);
log_printf (" usage=");
s = "";
if (prkdf->usageflags.encrypt) log_printf ("%sencrypt", s), s = ",";
if (prkdf->usageflags.decrypt) log_printf ("%sdecrypt", s), s = ",";
if (prkdf->usageflags.sign ) log_printf ("%ssign", s), s = ",";
if (prkdf->usageflags.sign_recover)
log_printf ("%ssign_recover", s), s = ",";
if (prkdf->usageflags.wrap ) log_printf ("%swrap", s), s = ",";
if (prkdf->usageflags.unwrap ) log_printf ("%sunwrap", s), s = ",";
if (prkdf->usageflags.verify ) log_printf ("%sverify", s), s = ",";
if (prkdf->usageflags.verify_recover)
log_printf ("%sverify_recover", s), s = ",";
if (prkdf->usageflags.derive ) log_printf ("%sderive", s), s = ",";
if (prkdf->usageflags.non_repudiation)
log_printf ("%snon_repudiation", s), s = ",";
log_printf ("\n");
/* Put it into the list. */
prkdf->next = prkdflist;
prkdflist = prkdf;
prkdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing PrKDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
if (prkdf)
{
xfree (prkdf->objid);
xfree (prkdf->authid);
xfree (prkdf);
}
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_prkdflist (prkdflist);
else
*result = prkdflist;
return err;
}
/* Read and parse the Certificate Directory Files identified by FID.
On success a newlist of CDF object gets stored at RESULT and the
caller is then responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_cdf (app_t app, unsigned short fid, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
cdf_object_t cdflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */
err = select_and_read_binary (app_get_slot (app), fid, "CDF",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Skip the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
pp += objlen;
nn -= objlen;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero path and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, (sizeof *cdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
cdf->objidlen = objidlen;
cdf->objid = xtrymalloc (objidlen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
cdf->pathlen = objlen/2;
for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2)
cdf->path[i] = ((pp[0] << 8) | pp[1]);
if (nn)
{
/* An index and length follows. */
cdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->len = ul;
}
log_debug ("CDF %04hX: id=", fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" path=");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%04hX", cdf->path[i]);
if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len);
log_printf ("\n");
/* Put it into the list. */
cdf->next = cdflist;
cdflist = cdf;
cdf = NULL;
continue; /* Ready. */
parse_error:
log_error ("error parsing CDF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
xfree (cdf);
err = 0;
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_cdflist (cdflist);
else
*result = cdflist;
return err;
}
/*
SEQUENCE {
SEQUENCE { -- CommonObjectAttributes
UTF8String 'specific PIN for DS'
BIT STRING 0 unused bits
'00000011'B
}
SEQUENCE { -- CommonAuthenticationObjectAttributes
OCTET STRING
07 -- iD
}
[1] { -- typeAttributes
SEQUENCE { -- PinAttributes
BIT STRING 0 unused bits
'0000100000110010'B -- local,initialized,needs-padding
-- exchangeRefData
ENUMERATED 1 -- ascii-numeric
INTEGER 6 -- minLength
INTEGER 6 -- storedLength
INTEGER 8 -- maxLength
[0]
02 -- pinReference
GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange
SEQUENCE {
OCTET STRING
3F 00 40 16 -- path to DF of PIN
}
}
}
}
*/
/* Read and parse an Authentication Object Directory File identified
by FID. On success a newlist of AODF objects gets stored at RESULT
and the caller is responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
aodf_object_t aodflist = NULL;
int i;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No authentication objects. */
err = select_and_read_binary (app_get_slot (app), fid, "AODF",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
/* FIXME: This shares a LOT of code with read_ef_prkdf! */
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
aodf_object_t aodf = NULL;
unsigned long ul;
const char *s;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing AODF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Allocate memory for a new AODF list item. */
aodf = xtrycalloc (1, sizeof *aodf);
if (!aodf)
goto no_core;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Search the optional AuthId. We need to skip the optional
Label (UTF8STRING) and the optional CommonObjectFlags
(BITSTRING). */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
if (tag == TAG_UTF8_STRING)
{
ppp += objlen; /* Skip the Label. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto no_authid;
if (err)
goto parse_error;
}
if (tag == TAG_OCTET_STRING && objlen)
{
aodf->authidlen = objlen;
aodf->authid = xtrymalloc (objlen);
if (!aodf->authid)
goto no_core;
memcpy (aodf->authid, ppp, objlen);
}
no_authid:
;
}
/* Parse the CommonAuthenticationObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
aodf->objidlen = objlen;
aodf->objid = xtrymalloc (objlen);
if (!aodf->objid)
goto no_core;
memcpy (aodf->objid, ppp, objlen);
}
/* Parse the typeAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* PinAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "biometric auth types are not supported"; break;
case 1: errstr = "authKey auth types are not supported"; break;
case 2: errstr = "external auth type are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
nn = objlen;
/* PinFlags */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || !objlen
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
unsigned int bits, mask;
int unused, full;
unused = *pp++; nn--; objlen--;
if ((!objlen && unused) || unused/8 > objlen)
{
err = gpg_error (GPG_ERR_ENCODING_PROBLEM);
goto parse_error;
}
full = objlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* The first octet */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
if ((bits & 0x80)) /* ASN.1 bit 0. */
aodf->pinflags.case_sensitive = 1;
if ((bits & 0x40)) /* ASN.1 bit 1. */
aodf->pinflags.local = 1;
if ((bits & 0x20))
aodf->pinflags.change_disabled = 1;
if ((bits & 0x10))
aodf->pinflags.unblock_disabled = 1;
if ((bits & 0x08))
aodf->pinflags.initialized = 1;
if ((bits & 0x04))
aodf->pinflags.needs_padding = 1;
if ((bits & 0x02))
aodf->pinflags.unblocking_pin = 1;
if ((bits & 0x01))
aodf->pinflags.so_pin = 1;
/* The second octet. */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
if ((bits & 0x80))
aodf->pinflags.disable_allowed = 1;
if ((bits & 0x40))
aodf->pinflags.integrity_protected = 1;
if ((bits & 0x20))
aodf->pinflags.confidentiality_protected = 1;
if ((bits & 0x10))
aodf->pinflags.exchange_ref_data = 1;
/* Skip remaining bits. */
pp += objlen;
nn -= objlen;
}
/* PinType */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pintype = ul;
/* minLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->min_length = ul;
/* storedLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->stored_length = ul;
/* optional maxLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->max_length = ul;
aodf->max_length_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional pinReference. */
if (class == CLASS_CONTEXT && tag == 0)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pin_reference = ul;
aodf->pin_reference_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional padChar. */
if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING)
{
if (objlen != 1)
{
errstr = "padChar is not of size(1)";
goto parse_error;
}
aodf->pad_char = *pp++; nn--;
aodf->pad_char_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Skip optional lastPinChange. */
if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional Path object. */
if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE)
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element is a non zero FID and of
even length (FID are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| !objlen || (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
aodf->pathlen = objlen/2;
aodf->path = xtrymalloc (aodf->pathlen);
if (!aodf->path)
goto no_core;
for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2)
aodf->path[i] = ((ppp[0] << 8) | ppp[1]);
if (nnn)
{
/* An index and length follows. */
aodf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->off = ul;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->len = ul;
}
}
- /* Igonore further objects which might be there due to future
+ /* Ignore further objects which might be there due to future
extensions of pkcs#15. */
ready:
log_debug ("AODF %04hX: id=", fid);
for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]);
if (aodf->authid)
{
log_printf (" authid=");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
log_printf (" flags=");
s = "";
if (aodf->pinflags.case_sensitive)
log_printf ("%scase_sensitive", s), s = ",";
if (aodf->pinflags.local)
log_printf ("%slocal", s), s = ",";
if (aodf->pinflags.change_disabled)
log_printf ("%schange_disabled", s), s = ",";
if (aodf->pinflags.unblock_disabled)
log_printf ("%sunblock_disabled", s), s = ",";
if (aodf->pinflags.initialized)
log_printf ("%sinitialized", s), s = ",";
if (aodf->pinflags.needs_padding)
log_printf ("%sneeds_padding", s), s = ",";
if (aodf->pinflags.unblocking_pin)
log_printf ("%sunblocking_pin", s), s = ",";
if (aodf->pinflags.so_pin)
log_printf ("%sso_pin", s), s = ",";
if (aodf->pinflags.disable_allowed)
log_printf ("%sdisable_allowed", s), s = ",";
if (aodf->pinflags.integrity_protected)
log_printf ("%sintegrity_protected", s), s = ",";
if (aodf->pinflags.confidentiality_protected)
log_printf ("%sconfidentiality_protected", s), s = ",";
if (aodf->pinflags.exchange_ref_data)
log_printf ("%sexchange_ref_data", s), s = ",";
{
char numbuf[50];
switch (aodf->pintype)
{
case PIN_TYPE_BCD: s = "bcd"; break;
case PIN_TYPE_ASCII_NUMERIC: s = "ascii-numeric"; break;
case PIN_TYPE_UTF8: s = "utf8"; break;
case PIN_TYPE_HALF_NIBBLE_BCD: s = "half-nibble-bcd"; break;
case PIN_TYPE_ISO9564_1: s = "iso9564-1"; break;
default:
sprintf (numbuf, "%lu", (unsigned long)aodf->pintype);
s = numbuf;
}
log_printf (" type=%s", s);
}
log_printf (" min=%lu", aodf->min_length);
log_printf (" stored=%lu", aodf->stored_length);
if (aodf->max_length_valid)
log_printf (" max=%lu", aodf->max_length);
if (aodf->pad_char_valid)
log_printf (" pad=0x%02x", aodf->pad_char);
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
if (aodf->pathlen)
{
log_printf (" path=");
for (i=0; i < aodf->pathlen; i++)
log_printf ("%04hX", aodf->path[i]);
if (aodf->have_off)
log_printf ("[%lu/%lu]", aodf->off, aodf->len);
}
log_printf ("\n");
/* Put it into the list. */
aodf->next = aodflist;
aodflist = aodf;
aodf = NULL;
continue; /* Ready. */
no_core:
err = gpg_error_from_syserror ();
release_aodf_object (aodf);
goto leave;
parse_error:
log_error ("error parsing AODF record (%d): %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
release_aodf_object (aodf);
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_aodflist (aodflist);
else
*result = aodflist;
return err;
}
/* Read and parse the EF(TokenInfo).
TokenInfo ::= SEQUENCE {
version INTEGER {v1(0)} (v1,...),
serialNumber OCTET STRING,
manufacturerID Label OPTIONAL,
label [0] Label OPTIONAL,
tokenflags TokenFlags,
seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL,
recordInfo [1] RecordInfo OPTIONAL,
supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL,
...,
issuerId [3] Label OPTIONAL,
holderId [4] Label OPTIONAL,
lastUpdate [5] LastUpdate OPTIONAL,
preferredLanguage PrintableString OPTIONAL -- In accordance with
-- IETF RFC 1766
} (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --})
TokenFlags ::= BIT STRING {
readOnly (0),
loginRequired (1),
prnGeneration (2),
eidCompliant (3)
}
5032:
30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T
72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic
65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card.
02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@....
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0 49: SEQUENCE {
2 1: INTEGER 0
5 4: OCTET STRING 05 45 36 9F
11 12: UTF8String 'D-Trust GmbH'
25 20: [0] 'Office identity card'
47 2: BIT STRING
: '00000010'B (bit 1)
: Error: Spurious zero bits in bitstring.
: }
*/
static gpg_error_t
read_ef_tokeninfo (app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
unsigned long ul;
err = select_and_read_binary (app_get_slot (app), 0x5032, "TokenInfo",
&buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("error parsing TokenInfo: %s\n", gpg_strerror (err));
goto leave;
}
n = objlen;
/* Version. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*p++) & 0xff;
n--;
}
if (ul)
{
log_error ("invalid version %lu in TokenInfo\n", ul);
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* serialNumber. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
xfree (app->app_local->serialno);
app->app_local->serialno = xtrymalloc (objlen);
if (!app->app_local->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->app_local->serialno, p, objlen);
app->app_local->serialnolen = objlen;
log_printhex (p, objlen, "Serialnumber from EF(TokenInfo) is:");
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the pkcs#15 card, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_p15_info (app_t app)
{
gpg_error_t err;
if (!read_ef_tokeninfo (app))
{
/* If we don't have a serial number yet but the TokenInfo provides
one, use that. */
if (!app->card->serialno && app->app_local->serialno)
{
app->card->serialno = app->app_local->serialno;
app->card->serialnolen = app->app_local->serialnolen;
app->app_local->serialno = NULL;
app->app_local->serialnolen = 0;
err = app_munge_serialno (app->card);
if (err)
return err;
}
}
/* Read the ODF so that we know the location of all directory
files. */
/* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */
err = read_ef_odf (app, 0x5031);
if (err)
return err;
/* Read certificate information. */
assert (!app->app_local->certificate_info);
assert (!app->app_local->trusted_certificate_info);
assert (!app->app_local->useful_certificate_info);
err = read_ef_cdf (app, app->app_local->odf.certificates,
&app->app_local->certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.trusted_certificates,
&app->app_local->trusted_certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.useful_certificates,
&app->app_local->useful_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about private keys. */
assert (!app->app_local->private_key_info);
err = read_ef_prkdf (app, app->app_local->odf.private_keys,
&app->app_local->private_key_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about authentication objects. */
assert (!app->app_local->auth_object_info);
err = read_ef_aodf (app, app->app_local->odf.auth_objects,
&app->app_local->auth_object_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (app_t app, ctrl_t ctrl, const char *certtype,
cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
buf = xtrymalloc (9 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (certinfo->objid, certinfo->objidlen, p);
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
NULL, (size_t)0);
xfree (buf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
keygrip gets returned in the caller provided 41 byte buffer
R_GRIPSTR. */
static gpg_error_t
keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
/* FIXME: We should check whether a public key directory file and a
matching public key for PRKDF is available. This should make
extraction of the key much easier. My current test card doesn't
have one, so we can only use the fallback solution bu looking for
a matching certificate and extract the key from there. */
/* Look for a matching certificate. A certificate matches if the Id
matches the one of the private key info. */
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == prkdf->objidlen
&& !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
return err;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, r_gripstr);
ksba_cert_release (cert);
return err;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. FIXME: much code duplication from
send_certinfo(). */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo)
{
gpg_error_t err;
for (; keyinfo; keyinfo = keyinfo->next)
{
char gripstr[40+1];
char *buf, *p;
int j;
buf = xtrymalloc (9 + keyinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (keyinfo->objid, keyinfo->objidlen, p);
err = keygripstr_from_prkdf (app, keyinfo, gripstr);
if (err)
{
log_error ("can't get keygrip from ");
for (j=0; j < keyinfo->pathlen; j++)
log_printf ("%04hX", keyinfo->path[j]);
log_printf (": %s\n", gpg_strerror (err));
}
else
{
assert (strlen (gripstr) == 40);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
buf, strlen (buf),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if ((flags & APP_LEARN_FLAG_KEYPAIRINFO))
err = 0;
else
{
err = send_certinfo (app, ctrl, "100", app->app_local->certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "101",
app->app_local->trusted_certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "102",
app->app_local->useful_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
return err;
}
/* Read a certifciate using the information in CDF and return the
certificate in a newly llocated buffer R_CERT and its length
R_CERTLEN. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
*r_cert = NULL;
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->image)
{
*r_cert = xtrymalloc (cdf->imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, cdf->image, cdf->imagelen);
*r_certlen = cdf->imagelen;
return 0;
}
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = select_ef_by_path (app, cdf->path, cdf->pathlen);
if (err)
goto leave;
err = iso7816_read_binary (app_get_slot (app), cdf->off, cdf->len,
&buffer, &buflen);
if (!err && (!buflen || *buffer == 0xff))
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("error reading certificate with Id ");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
/* Try to cache it. */
if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen)))
{
memcpy (cdf->image, *r_cert, *r_certlen);
cdf->imagelen = *r_certlen;
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!strcmp (name, "$AUTHKEYID"))
{
char *buf, *p;
prkdf_object_t prkdf;
/* We return the ID of the first private keycapable of
signing. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->usageflags.sign)
break;
if (prkdf)
{
buf = xtrymalloc (9 + prkdf->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (prkdf->objid, prkdf->objidlen, p);
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
return 0;
}
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
/* For certain cards we return special IDs. There is no
general rule for it so we need to decide case by case. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
/* The eID card has a card number printed on the front matter
which seems to be a good indication. */
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 };
err = select_ef_by_path (app, path, DIM(path) );
if (!err)
err = iso7816_read_binary (app_get_slot (app), 0, 0,
&buffer, &buflen);
if (err)
{
log_error ("error accessing EF(ID): %s\n", gpg_strerror (err));
return err;
}
p = find_tlv (buffer, buflen, 1, &n);
if (p && n == 12)
{
char tmp[12+2+1];
memcpy (tmp, p, 3);
tmp[3] = '-';
memcpy (tmp+4, p+3, 7);
tmp[11] = '-';
memcpy (tmp+12, p+10, 2);
tmp[14] = 0;
send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0);
xfree (buffer);
return 0;
}
xfree (buffer);
}
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Micardo cards require special treatment. This is a helper for the
crypto functions to manage the security environment. We expect that
the key file has already been selected. FID is the one of the
selected key. */
static gpg_error_t
micardo_mse (app_t app, unsigned short fid)
{
gpg_error_t err;
int recno;
unsigned short refdata = 0;
int se_num;
unsigned char msebuf[10];
/* Read the KeyD file containing extra information on keys. */
err = iso7816_select_file (app_get_slot (app), 0x0013, 0);
if (err)
{
log_error ("error reading EF_keyD: %s\n", gpg_strerror (err));
return err;
}
for (recno = 1, se_num = -1; ; recno++)
{
unsigned char *buffer;
size_t buflen;
size_t n, nn;
const unsigned char *p, *pp;
err = iso7816_read_record (app_get_slot (app), recno, 1, 0,
&buffer, &buflen);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
break; /* ready */
if (err)
{
log_error ("error reading EF_keyD record: %s\n",
gpg_strerror (err));
return err;
}
log_printhex (buffer, buflen, "keyD record:");
p = find_tlv (buffer, buflen, 0x83, &n);
if (p && n == 4 && ((p[2]<<8)|p[3]) == fid)
{
refdata = ((p[0]<<8)|p[1]);
/* Locate the SE DO and the there included sec env number. */
p = find_tlv (buffer, buflen, 0x7b, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x80, &nn);
if (pp && nn == 1)
{
se_num = *pp;
xfree (buffer);
break; /* found. */
}
}
}
xfree (buffer);
}
if (se_num == -1)
{
log_error ("CRT for keyfile %04hX not found\n", fid);
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Restore the security environment to SE_NUM if needed */
if (se_num)
{
err = iso7816_manage_security_env (app_get_slot (app),
0xf3, se_num, NULL, 0);
if (err)
{
log_error ("restoring SE to %d failed: %s\n",
se_num, gpg_strerror (err));
return err;
}
}
/* Set the DST reference data. */
msebuf[0] = 0x83;
msebuf[1] = 0x03;
msebuf[2] = 0x80;
msebuf[3] = (refdata >> 8);
msebuf[4] = refdata;
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xb6, msebuf, 5);
if (err)
{
log_error ("setting SE to reference file %04hX failed: %s\n",
refdata, gpg_strerror (err));
return err;
}
return 0;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument. */
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
gpg_error_t err;
int i;
unsigned char data[36]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix above and also
fit the 36 bytes of md5sha1. */
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
int no_data_padding = 0; /* True if the card want the data without padding.*/
int mse_done = 0; /* Set to true if the MSE has been done. */
(void)ctrl;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen != 20 && indatalen != 16 && indatalen != 35 && indatalen != 36)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
||prkdf->usageflags.non_repudiation))
{
log_error ("key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (!prkdf->authid)
{
log_error ("no authentication object defined for %s\n", keyidstr);
/* fixme: we might want to go ahead and do without PIN
verification. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
/* Find the authentication object to this private key object. */
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf)
{
log_error ("authentication object for %s missing\n", keyidstr);
return gpg_error (GPG_ERR_INV_CARD);
}
if (aodf->authid)
{
log_error ("PIN verification is protected by an "
"additional authentication token\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pinflags.integrity_protected
|| aodf->pinflags.confidentiality_protected)
{
log_error ("PIN verification requires unsupported protection method\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (!aodf->stored_length && aodf->pinflags.needs_padding)
{
log_error ("PIN verification requires padding but no length known\n");
return gpg_error (GPG_ERR_INV_CARD);
}
/* Select the key file. Note that this may change the security
environment thus we do it before PIN verification. */
err = select_ef_by_path (app, prkdf->path, prkdf->pathlen);
if (err)
{
log_error ("error selecting file for key %s: %s\n",
keyidstr, gpg_strerror (errno));
return err;
}
/* Due to the fact that the non-repudiation signature on a BELPIC
card requires a verify immediately before the DSO we set the
MSE before we do the verification. Other cards might also allow
this but I don't want to break anything, thus we do it only
for the BELPIC card here. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
unsigned char mse[5];
mse[0] = 4; /* Length of the template. */
mse[1] = 0x80; /* Algorithm reference tag. */
if (hashalgo == MD_USER_TLS_MD5SHA1)
mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */
else
mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */
mse[3] = 0x84; /* Private key reference tag. */
mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82;
err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
no_data_padding = 1;
mse_done = 1;
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
/* Now that we have all the information available, prepare and run
the PIN verification.*/
if (1)
{
char *pinvalue;
size_t pinvaluelen;
const char *errstr;
const char *s;
if (prkdf->usageflags.non_repudiation
&& app->app_local->card_type == CARD_TYPE_BELPIC)
err = pincb (pincb_arg, "PIN (qualified signature!)", &pinvalue);
else
err = pincb (pincb_arg, "PIN", &pinvalue);
if (err)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
/* We might need to cope with UTF8 things here. Not sure how
min_length etc. are exactly defined, for now we take them as
a plain octet count. */
if (strlen (pinvalue) < aodf->min_length)
{
log_error ("PIN is too short; minimum length is %lu\n",
aodf->min_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length)
{
/* This would otherwise truncate the PIN silently. */
log_error ("PIN is too large; maximum length is %lu\n",
aodf->stored_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length)
{
log_error ("PIN is too large; maximum length is %lu\n",
aodf->max_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
if (err)
{
xfree (pinvalue);
return err;
}
errstr = NULL;
err = 0;
switch (aodf->pintype)
{
case PIN_TYPE_BCD:
case PIN_TYPE_ASCII_NUMERIC:
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
errstr = "Non-numeric digits found in PIN";
err = gpg_error (GPG_ERR_BAD_PIN);
}
break;
case PIN_TYPE_UTF8:
break;
case PIN_TYPE_HALF_NIBBLE_BCD:
errstr = "PIN type Half-Nibble-BCD is not supported";
break;
case PIN_TYPE_ISO9564_1:
errstr = "PIN type ISO9564-1 is not supported";
break;
default:
errstr = "Unknown PIN type";
break;
}
if (errstr)
{
log_error ("can't verify PIN: %s\n", errstr);
xfree (pinvalue);
return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pintype == PIN_TYPE_BCD )
{
char *paddedpin;
int ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < aodf->stored_length && *s)
paddedpin[i++] = (((*s - '0') << 4)
|((aodf->pad_char_valid?aodf->pad_char:0)&0x0f));
if (aodf->pinflags.needs_padding)
while (i < aodf->stored_length)
paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0;
xfree (pinvalue);
pinvalue = paddedpin;
pinvaluelen = i;
}
else if (aodf->pinflags.needs_padding)
{
char *paddedpin;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++)
paddedpin[i] = *s;
/* Not sure what padding char to use if none has been set.
For now we use 0x00; maybe a space would be better. */
for (; i < aodf->stored_length; i++)
paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0;
paddedpin[i] = 0;
pinvaluelen = i;
xfree (pinvalue);
pinvalue = paddedpin;
}
else
pinvaluelen = strlen (pinvalue);
err = iso7816_verify (app_get_slot (app),
aodf->pin_reference_valid? aodf->pin_reference : 0,
pinvalue, pinvaluelen);
xfree (pinvalue);
if (err)
{
log_error ("PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
log_debug ("PIN verification succeeded\n");
}
/* Prepare the DER object from INDATA. */
if (indatalen == 36)
{
/* No ASN.1 container used. */
if (hashalgo != MD_USER_TLS_MD5SHA1)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. Check that it is what we want and that
it matches the hash algorithm. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160
&& !memcmp (indata, rmd160_prefix, 15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
}
else
{
/* Need to prepend the prefix. */
if (hashalgo == GCRY_MD_SHA1)
memcpy (data, sha1_prefix, 15);
else if (hashalgo == GCRY_MD_RMD160)
memcpy (data, rmd160_prefix, 15);
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data+15, indata, indatalen);
}
/* Manage security environment needs to be weaked for certain cards. */
if (mse_done)
err = 0;
else if (app->app_local->card_type == CARD_TYPE_TCOS)
{
/* TCOS creates signatures always using the local key 0. MSE
may not be used. */
}
else if (app->app_local->card_type == CARD_TYPE_MICARDO)
{
if (!prkdf->pathlen)
err = gpg_error (GPG_ERR_BUG);
else
err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]);
}
else if (prkdf->key_reference_valid)
{
unsigned char mse[3];
mse[0] = 0x84; /* Select asym. key. */
mse[1] = 1;
mse[2] = prkdf->key_reference;
err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
}
if (err)
{
log_error ("MSE failed: %s\n", gpg_strerror (err));
return err;
}
if (hashalgo == MD_USER_TLS_MD5SHA1)
err = iso7816_compute_ds (app_get_slot (app),
0, data, 36, 0, outdata, outdatalen);
else if (no_data_padding)
err = iso7816_compute_ds (app_get_slot (app),
0, data+15, 20, 0,outdata,outdatalen);
else
err = iso7816_compute_ds (app_get_slot (app),
0, data, 35, 0, outdata, outdatalen);
return err;
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!prkdf->usageflags.sign)
{
log_error ("key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Assume that EF(DIR) has been selected. Read its content and figure
out the home EF of pkcs#15. Return that home DF or 0 if not found
and the value at the address of BELPIC indicates whether it was
found by the belpic aid. */
static unsigned short
read_home_df (int slot, int *r_belpic)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p, *pp;
size_t buflen, n, nn;
unsigned short result = 0;
*r_belpic = 0;
err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("error reading EF{DIR}: %s\n", gpg_strerror (err));
return 0;
}
/* FIXME: We need to scan all records. */
p = find_tlv (buffer, buflen, 0x61, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x4f, &nn);
if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn))
|| (*r_belpic = (nn == sizeof pkcs15be_aid
&& !memcmp (pp, pkcs15be_aid, nn)))))
{
pp = find_tlv (p, n, 0x50, &nn);
if (pp) /* fixme: Filter log value? */
log_info ("pkcs#15 application label from EF(DIR) is '%.*s'\n",
(int)nn, pp);
pp = find_tlv (p, n, 0x51, &nn);
if (pp && nn == 4 && *pp == 0x3f && !pp[1])
{
result = ((pp[2] << 8) | pp[3]);
log_info ("pkcs#15 application directory is 0x%04hX\n", result);
}
}
}
xfree (buffer);
return result;
}
/*
Select the PKCS#15 application on the card in SLOT.
*/
gpg_error_t
app_select_p15 (app_t app)
{
int slot = app_get_slot (app);
int rc;
unsigned short def_home_df = 0;
card_type_t card_type = CARD_TYPE_UNKNOWN;
int direct = 0;
int is_belpic = 0;
rc = iso7816_select_application (slot, pkcs15_aid, sizeof pkcs15_aid, 0);
if (rc)
{ /* Not found: Try to locate it from 2F00. We use direct path
selection here because it seems that the Belgian eID card
does only allow for that. Many other cards supports this
selection method too. Note, that we don't use
select_application above for the Belgian card - the call
works but it seems that it did not switch to the correct DF.
Using the 2f02 just works. */
unsigned short path[1] = { 0x2f00 };
rc = iso7816_select_path (app_get_slot (app), path, 1);
if (!rc)
{
direct = 1;
def_home_df = read_home_df (slot, &is_belpic);
if (def_home_df)
{
path[0] = def_home_df;
rc = iso7816_select_path (app_get_slot (app), path, 1);
}
}
}
if (rc)
{ /* Still not found: Try the default DF. */
def_home_df = 0x5015;
rc = iso7816_select_file (slot, def_home_df, 1);
}
if (!rc)
{
/* Determine the type of the card. The general case is to look
it up from the ATR table. For the Belgian eID card we know
it instantly from the AID. */
if (is_belpic)
{
card_type = CARD_TYPE_BELPIC;
}
else
{
unsigned char *atr;
size_t atrlen;
int i;
atr = apdu_get_atr (app_get_slot (app), &atrlen);
if (!atr)
rc = gpg_error (GPG_ERR_INV_CARD);
else
{
for (i=0; card_atr_list[i].atrlen; i++)
if (card_atr_list[i].atrlen == atrlen
&& !memcmp (card_atr_list[i].atr, atr, atrlen))
{
card_type = card_atr_list[i].type;
break;
}
xfree (atr);
}
}
}
if (!rc)
{
app->apptype = APPTYPE_P15;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* Set the home DF. Note that we currently can't do that if the
selection via application ID worked. This will store 0 there
instead. FIXME: We either need to figure the home_df via the
DIR file or using the return values from the select file
APDU. */
app->app_local->home_df = def_home_df;
/* Store the card type. FIXME: We might want to put this into
the common APP structure. */
app->app_local->card_type = card_type;
/* Store whether we may and should use direct path selection. */
app->app_local->direct_path_selection = direct;
/* Read basic information and thus check whether this is a real
card. */
rc = read_p15_info (app);
if (rc)
goto leave;
/* Special serial number munging. We need to check for a German
prototype card right here because we need to access to
EF(TokenInfo). We mark such a serial number by the using a
prefix of FF0100. */
if (app->card->serialnolen == 12
&& !memcmp (app->card->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12))
{
/* This is a German card with a silly serial number. Try to get
the serial number from the EF(TokenInfo). . */
unsigned char *p;
/* FIXME: actually get it from EF(TokenInfo). */
p = xtrymalloc (3 + app->card->serialnolen);
if (!p)
rc = gpg_error (gpg_err_code_from_errno (errno));
else
{
memcpy (p, "\xff\x01", 3);
memcpy (p+3, app->card->serialno, app->card->serialnolen);
app->card->serialnolen += 3;
xfree (app->card->serialno);
app->card->serialno = p;
}
}
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = NULL;
app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = NULL;
app->fnc.change_pin = NULL;
app->fnc.check_pin = NULL;
leave:
if (rc)
do_deinit (app);
}
return rc;
}
diff --git a/scd/app-piv.c b/scd/app-piv.c
index cefc9d997..38608f47b 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -1,3702 +1,3702 @@
/* app-piv.c - The OpenPGP card application.
* Copyright (C) 2019, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Some notes:
* - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4
* - https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
*
* - Access control matrix:
* | Action | 9B | PIN | PUK | |
* |--------------+-----+-----+-----+------------------------------|
* | Generate key | yes | | | |
* | Change 9B | yes | | | |
* | Change retry | yes | yes | | Yubikey only |
* | Import key | yes | | | |
* | Import cert | yes | | | |
* | Change CHUID | yes | | | |
* | Reset card | | | | PIN and PUK in blocked state |
* | Verify PIN | | yes | | |
* | Sign data | | yes | | |
* | Decrypt data | | yes | | |
* | Change PIN | | yes | | |
* | Change PUK | | | yes | |
* | Unblock PIN | | | yes | New PIN required |
* |---------------------------------------------------------------|
* (9B indicates the 24 byte PIV Card Application Administration Key)
*
* - When generating a key we store the created public key in the
* corresponding data object, so that gpg and gpgsm are able to get
* the public key, create a certificate and store that then in that
* data object. That is not standard compliant but due to the use
* of other tags, it should not harm. See do_genkey for the actual
* used tag structure.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "apdu.h" /* We use apdu_send_direct. */
#define PIV_ALGORITHM_3DES_ECB_0 0x00
#define PIV_ALGORITHM_2DES_ECB 0x01
#define PIV_ALGORITHM_2DES_CBC 0x02
#define PIV_ALGORITHM_3DES_ECB 0x03
#define PIV_ALGORITHM_3DES_CBC 0x04
#define PIV_ALGORITHM_RSA 0x07
#define PIV_ALGORITHM_AES128_ECB 0x08
#define PIV_ALGORITHM_AES128_CBC 0x09
#define PIV_ALGORITHM_AES192_ECB 0x0A
#define PIV_ALGORITHM_AES192_CBC 0x0B
#define PIV_ALGORITHM_AES256_ECB 0x0C
#define PIV_ALGORITHM_AES256_CBC 0x0D
#define PIV_ALGORITHM_ECC_P256 0x11
#define PIV_ALGORITHM_ECC_P384 0x14
/* The AID for PIV. */
static char const piv_aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */
0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ };
/* A table describing the DOs of a PIV card. */
struct data_object_s
{
unsigned int tag;
unsigned int mandatory:1;
unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */
unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN,
3=VCIand(PINorOCC) */
unsigned int dont_cache:1; /* Data item will not be cached. */
unsigned int flush_on_error:1; /* Flush cached item on error. */
unsigned int keypair:1; /* Has a public key for a keypair. */
const char keyref[3]; /* The key reference. */
const char *oidsuffix; /* Suffix of the OID. */
const char *usage; /* Usage string for a keypair or NULL. */
const char *desc; /* Description of the DO. */
};
typedef struct data_object_s *data_object_t;
static struct data_object_s data_objects[] = {
{ 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL,
"Card Capability Container"},
{ 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL,
"Cardholder Unique Id" },
{ 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a",
"Cert PIV Authentication" },
{ 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL,
"Cardholder Fingerprints" },
{ 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL,
"Security Object" },
{ 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL,
"Cardholder Facial Image" },
{ 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a",
"Cert Card Authentication"},
{ 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc",
"Cert Digital Signature" },
{ 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e",
"Cert Key Management" },
{ 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL,
"Printed Information" },
{ 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL,
"Discovery Object" },
{ 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL,
"Key History Object" },
{ 0x5FC10D, 0, 0,1, 0,0, 0, "82", "2.16.1", "e",
"Retired Cert Key Mgm 1" },
{ 0x5FC10E, 0, 0,1, 0,0, 0, "83", "2.16.2", "e",
"Retired Cert Key Mgm 2" },
{ 0x5FC10F, 0, 0,1, 0,0, 0, "84", "2.16.3", "e",
"Retired Cert Key Mgm 3" },
{ 0x5FC110, 0, 0,1, 0,0, 0, "85", "2.16.4", "e",
"Retired Cert Key Mgm 4" },
{ 0x5FC111, 0, 0,1, 0,0, 0, "86", "2.16.5", "e",
"Retired Cert Key Mgm 5" },
{ 0x5FC112, 0, 0,1, 0,0, 0, "87", "2.16.6", "e",
"Retired Cert Key Mgm 6" },
{ 0x5FC113, 0, 0,1, 0,0, 0, "88", "2.16.7", "e",
"Retired Cert Key Mgm 7" },
{ 0x5FC114, 0, 0,1, 0,0, 0, "89", "2.16.8", "e",
"Retired Cert Key Mgm 8" },
{ 0x5FC115, 0, 0,1, 0,0, 0, "8A", "2.16.9", "e",
"Retired Cert Key Mgm 9" },
{ 0x5FC116, 0, 0,1, 0,0, 0, "8B", "2.16.10", "e",
"Retired Cert Key Mgm 10" },
{ 0x5FC117, 0, 0,1, 0,0, 0, "8C", "2.16.11", "e",
"Retired Cert Key Mgm 11" },
{ 0x5FC118, 0, 0,1, 0,0, 0, "8D", "2.16.12", "e",
"Retired Cert Key Mgm 12" },
{ 0x5FC119, 0, 0,1, 0,0, 0, "8E", "2.16.13", "e",
"Retired Cert Key Mgm 13" },
{ 0x5FC11A, 0, 0,1, 0,0, 0, "8F", "2.16.14", "e",
"Retired Cert Key Mgm 14" },
{ 0x5FC11B, 0, 0,1, 0,0, 0, "90", "2.16.15", "e",
"Retired Cert Key Mgm 15" },
{ 0x5FC11C, 0, 0,1, 0,0, 0, "91", "2.16.16", "e",
"Retired Cert Key Mgm 16" },
{ 0x5FC11D, 0, 0,1, 0,0, 0, "92", "2.16.17", "e",
"Retired Cert Key Mgm 17" },
{ 0x5FC11E, 0, 0,1, 0,0, 0, "93", "2.16.18", "e",
"Retired Cert Key Mgm 18" },
{ 0x5FC11F, 0, 0,1, 0,0, 0, "94", "2.16.19", "e",
"Retired Cert Key Mgm 19" },
{ 0x5FC120, 0, 0,1, 0,0, 0, "95", "2.16.20", "e",
"Retired Cert Key Mgm 20" },
{ 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL,
"Cardholder Iris Images" },
{ 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL,
"BIT Group Template" },
{ 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL,
"SM Cert Signer" },
{ 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL,
"Pairing Code Ref Data" },
{ 0 }
/* Other key reference values without a data object:
* "00" Global PIN (not cleared by application switching)
* "04" PIV Secure Messaging Key
* "80" PIV Application PIN
* "81" PIN Unblocking Key
* "96" Primary Finger OCC
* "97" Secondary Finger OCC
* "98" Pairing Code
* "9B" PIV Card Application Administration Key
*
* Yubikey specific data objects:
* "F9" Attestation key (preloaded can be replaced)
*/
};
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Various flags. */
struct
{
unsigned int yubikey:1; /* This is on a Yubikey. */
} flags;
/* Keep track on whether we cache a certain PIN so that we get it
* from the cache only if we know we cached it. This inhibits the
* use of the same cache entry for a card plugged in and out without
* gpg-agent having noticed that due to a bug. */
struct
{
unsigned int maybe_00:1;
unsigned int maybe_80:1;
unsigned int maybe_81:1;
unsigned int maybe_96:1;
unsigned int maybe_97:1;
unsigned int maybe_98:1;
unsigned int maybe_9B:1;
} pincache;
};
/***** Local prototypes *****/
static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *got_cert);
static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp);
static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen,
int mechanism, gcry_sexp_t *r_sexp);
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
* from the cache. With GET_IMMEDIATE passed as true, the cache is
* bypassed. The tag-53 container is also removed. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate)
{
gpg_error_t err;
int i;
unsigned char *p;
const unsigned char *s;
size_t len, n;
struct cache_s *c;
*result = NULL;
*resultlen = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
err = iso7816_get_data_odd (app_get_slot (app), 0, tag, &p, &len);
if (err)
return err;
/* Unless the Discovery Object or the BIT Group Template is
* requested, remove the outer container.
* (SP800-73.4 Part 2, section 3.1.2) */
if (tag == 0x7E || tag == 0x7F61)
;
else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n)))
{
memmove (p, s, n);
len = n;
}
if (len)
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
log_assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
if (len)
memcpy (c->data, p, len);
else
xfree (p);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove data object described by TAG from the cache. If TAG is 0
* all cache iterms are flushed. */
static void
flush_cached_data (app_t app, int tag)
{
struct cache_s *c, *cprev;
for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next)
if (c->tag == tag || !tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
log_assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
* buffer with its content in RESULT and NBYTES. The return value is
* NULL if not found or a pointer which must be used to release the
* buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_err)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
gpg_error_t dummyerr;
if (!r_err)
r_err = &dummyerr;
*result = NULL;
*nbytes = 0;
*r_err = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
value = NULL;
err = gpg_error (GPG_ERR_ENOENT);
if (!value) /* Not in a constructed DO, try simple. */
{
err = get_cached_data (app, tag, &buffer, &buflen,
data_objects[i].dont_cache);
if (!err)
{
value = buffer;
valuelen = buflen;
}
}
if (!err)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_err = err;
return NULL;
}
static void
dump_all_do (int slot)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
err = iso7816_get_data_odd (slot, 0, data_objects[i].tag,
&buffer, &buflen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT
&& !data_objects[i].mandatory)
;
else
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (err));
}
else
{
if (data_objects[i].tag == 0x5FC109)
log_info ("DO '%s': '%.*s'\n", data_objects[i].desc,
(int)buflen, buffer);
else
{
log_info ("DO '%s': ", data_objects[i].desc);
if (buflen > 16 && opt.verbose < 2)
{
log_printhex (buffer, 16, NULL);
log_printf ("[...]\n");
}
else
log_printhex (buffer, buflen, "");
}
}
xfree (buffer); buffer = NULL;
}
}
/* Create a TLV tag and value and store it at BUFFER. Return the
* length of tag and length. A LENGTH greater than 65535 is
* truncated. TAG must be less or equal to 2^16. If BUFFER is NULL,
* only the required length is computed. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
if (length > 0xffff)
length = 0xffff;
if (buffer)
{
unsigned char *p = buffer;
if (tag > 0xff)
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
else
{
size_t n = 0;
if (tag > 0xff)
n++;
n++;
if (length < 128)
n++;
else if (length < 256)
n += 2;
else
n += 3;
return n;
}
}
/* Function to build a list of TLV and return the result in a mallcoed
* buffer. The varargs are tuples of (int,size_t,void) each with the
* tag, the length and the actual data. A (0,0,NULL) tuple terminates
* the list. Up to 10 tuples are supported. If SECMEM is true the
* returned buffer is allocated in secure memory. */
static gpg_error_t
concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
unsigned int len;
unsigned int contlen;
const void *data;
} argv[10];
int i, j, argc;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
*r_result = NULL;
*r_resultlen = 0;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, r_resultlen);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].contlen = 0;
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
err = gpg_error (GPG_ERR_EINVAL);
goto leave;
}
argc++;
}
va_end (arg_ptr);
/* Compute the required buffer length and allocate the buffer. */
datalen = 0;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. Compute its length. Note that we
* currently allow only one constructed tag in the list. */
for (n=0, j = i + 1; j < argc; j++)
{
log_assert (!(!argv[j].len && !argv[j].data));
n += add_tlv (NULL, argv[j].tag, argv[j].len);
n += argv[j].len;
}
argv[i].contlen = n;
datalen += add_tlv (NULL, argv[i].tag, n);
}
else
{
datalen += add_tlv (NULL, argv[i].tag, argv[i].len);
datalen += argv[i].len;
}
}
data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. */
p += add_tlv (p, argv[i].tag, argv[i].contlen);
}
else
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
}
log_assert ( data + datalen == p );
*r_result = data;
data = NULL;
*r_resultlen = datalen;
err = 0;
leave:
xfree (data);
return err;
}
/* Wrapper around iso7816_put_data_odd which also sets the tag into
* the '5C' data object. The varargs are tuples of (int,size_t,void)
* with the tag, the length and the actual data. A (0,0,NULL) tuple
* terminates the list. Up to 10 tuples are supported. */
static gpg_error_t
put_data (int slot, unsigned int tag, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
size_t len;
const void *data;
} argv[10];
int i, argc;
unsigned char data5c[5];
size_t data5clen;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, tag);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
return GPG_ERR_EINVAL;
}
argc++;
}
va_end (arg_ptr);
/* Build the TLV with the tag to be updated. */
data5c[0] = 0x5c; /* Tag list */
if (tag <= 0xff)
{
data5c[1] = 1;
data5c[2] = tag;
data5clen = 3;
}
else if (tag <= 0xffff)
{
data5c[1] = 2;
data5c[2] = (tag >> 8);
data5c[3] = tag;
data5clen = 4;
}
else
{
data5c[1] = 3;
data5c[2] = (tag >> 16);
data5c[3] = (tag >> 8);
data5c[4] = tag;
data5clen = 5;
}
/* Compute the required buffer length and allocate the buffer. */
n = 0;
for (i=0; i < argc; i++)
{
n += add_tlv (NULL, argv[i].tag, argv[i].len);
n += argv[i].len;
}
datalen = data5clen + add_tlv (NULL, 0x53, n) + n;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
memcpy (p, data5c, data5clen);
p += data5clen;
p += add_tlv (p, 0x53, n);
for (i=0; i < argc; i++)
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
log_assert ( data + datalen == p );
err = iso7816_put_data_odd (slot, -1 /* use command chaining */,
0x3fff, data, datalen);
leave:
xfree (data);
return err;
}
/* Parse the key reference KEYREFSTR which is expected to hold a key
* reference for a CHV object. Return the one octet keyref or -1 for
* an invalid reference. */
static int
parse_chv_keyref (const char *keyrefstr)
{
if (!keyrefstr)
return -1;
else if (!ascii_strcasecmp (keyrefstr, "PIV.00"))
return 0x00;
else if (!ascii_strcasecmp (keyrefstr, "PIV.80"))
return 0x80;
else if (!ascii_strcasecmp (keyrefstr, "PIV.81"))
return 0x81;
else
return -1;
}
/* Return an allocated string with the serial number in a format to be
* show to the user. With FAILMODE is true return NULL if such an
* abbreviated S/N is not available, else return the full serial
* number as a hex string. May return NULL on malloc problem. */
static char *
get_dispserialno (app_t app, int failmode)
{
char *result;
if (app->card && app->card->serialno && app->card->serialnolen == 3+1+4
&& !memcmp (app->card->serialno, "\xff\x02\x00", 3))
{
/* This is a 4 byte S/N of a Yubikey which seems to be printed
* on the token in decimal. Maybe they will print larger S/N
* also in decimal but we can't be sure, thus do it only for
* these 32 bit numbers. */
unsigned long sn;
sn = app->card->serialno[4] * 16777216;
sn += app->card->serialno[5] * 65536;
sn += app->card->serialno[6] * 256;
sn += app->card->serialno[7];
result = xtryasprintf ("yk-%lu", sn);
}
else if (failmode)
result = NULL; /* No Abbreviated S/N. */
else
result = app_get_serialno (app);
return result;
}
/* The verify command can be used to retrieve the security status of
* the card. Given the PIN name (e.g. "PIV.80" for the application
* pin, a status is returned:
*
* -1 = Error retrieving the data,
* -2 = No such PIN,
* -3 = PIN blocked,
* -5 = Verified and still valid,
* n >= 0 = Number of verification attempts left.
*/
static int
get_chv_status (app_t app, const char *keyrefstr)
{
unsigned char apdu[4];
unsigned int sw;
int result;
int keyref;
keyref = parse_chv_keyref (keyrefstr);
if (!keyrefstr)
return -1;
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
result = -5; /* No need to verification. */
else if (sw == 0x6a88 || sw == 0x6a80)
result = -2; /* No such PIN. */
else if (sw == 0x6983)
result = -3; /* PIN is blocked. */
else if ((sw & 0xfff0) == 0x63C0)
result = (sw & 0x000f);
else
result = -1; /* Error. */
return result;
}
/* Implementation of the GETATTR command. This is similar to the
* LEARN command but returns only one value via status lines. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "SERIALNO", 0x0000, -1 },
{ "$AUTHKEYID", 0x0000, -2 }, /* Default ssh key. */
{ "$ENCRKEYID", 0x0000, -6 }, /* Default encryption key. */
{ "$SIGNKEYID", 0x0000, -7 }, /* Default signing key. */
{ "$DISPSERIALNO",0x0000, -3 },
{ "CHV-STATUS", 0x0000, -4 },
{ "CHV-USAGE", 0x007E, -5 }
};
gpg_error_t err = 0;
int idx;
void *relptr;
unsigned char *value;
size_t valuelen;
const unsigned char *s;
size_t n;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
err = gpg_error (GPG_ERR_INV_NAME);
else if (table[idx].special == -1)
{
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
}
else if (table[idx].special == -2)
{
char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else if (table[idx].special == -3)
{
char *tmp = get_dispserialno (app, 1);
if (tmp)
{
send_status_info (ctrl, table[idx].name,
tmp, strlen (tmp),
NULL, (size_t)0);
xfree (tmp);
}
else
err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */
}
else if (table[idx].special == -4) /* CHV-STATUS */
{
int tmp[4];
tmp[0] = get_chv_status (app, "PIV.00");
tmp[1] = get_chv_status (app, "PIV.80");
tmp[2] = get_chv_status (app, "PIV.81");
err = send_status_printf (ctrl, table[idx].name, "%d %d %d",
tmp[0], tmp[1], tmp[2]);
}
else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */
{
/* We return 2 hex bytes or nothing in case the discovery object
* is not supported. */
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
s = find_tlv (value, valuelen, 0x7E, &n);
if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 )
err = send_status_printf (ctrl, table[idx].name, "%02X %02X",
s[0], s[1]);
xfree (relptr);
}
}
else if (table[idx].special == -6)
{
char const tmp[] = "PIV.9D"; /* Key Management. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else if (table[idx].special == -7)
{
char const tmp[] = "PIV.9C"; /* Digital Signature. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else
{
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
}
return err;
}
/* Authenticate the card using the Card Application Administration
* Key. (VALUE,VALUELEN) has that 24 byte key. */
static gpg_error_t
auth_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char tmpl[4+24];
size_t tmpllen;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
char witness[8];
size_t n;
gcry_cipher_hd_t cipher = NULL;
/* Prepare decryption. */
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipher, value, valuelen);
if (err)
goto leave;
/* Request a witness. */
tmpl[0] = 0x7c;
tmpl[1] = 0x02;
tmpl[2] = 0x80;
tmpl[3] = 0; /* (Empty witness requests a witness.) */
tmpllen = 4;
err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x80, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper witness received\n");
goto leave;
}
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
/* Return decrypted witness and send our challenge. */
tmpl[0] = 0x7c;
tmpl[1] = 22;
tmpl[2] = 0x80;
tmpl[3] = 8;
memcpy (tmpl+4, witness, 8);
tmpl[12] = 0x81;
tmpl[13] = 8;
gcry_create_nonce (tmpl+14, 8);
tmpl[22] = 0x80;
tmpl[23] = 0;
tmpllen = 24;
xfree (outdata);
err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper challenge received\n");
goto leave;
}
/* (We reuse the witness buffer.) */
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
if (memcmp (witness, tmpl+14, 8))
{
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
leave:
xfree (outdata);
gcry_cipher_close (cipher);
return err;
}
/* Set a new admin key. */
static gpg_error_t
set_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char apdu[8+24];
unsigned int sw;
/* Check whether it is a weak key and that it is of proper length. */
{
gcry_cipher_hd_t cipher;
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (!err)
{
err = gcry_cipher_setkey (cipher, value, valuelen);
gcry_cipher_close (cipher);
}
if (err)
goto leave;
}
if (app->app_local->flags.yubikey)
{
/* This is a Yubikey. */
if (valuelen != 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
/* We use a proprietary Yubikey command. */
apdu[0] = 0;
apdu[1] = 0xff;
apdu[2] = 0xff;
apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */
apdu[4] = 3 + 24;
apdu[5] = PIV_ALGORITHM_3DES_ECB;
apdu[6] = 0x9b;
apdu[7] = 24;
memcpy (apdu+8, value, 24);
err = iso7816_apdu_direct (app_get_slot (app), apdu, 8+24, 0,
&sw, NULL, NULL);
wipememory (apdu+8, 24);
if (err)
log_error ("piv: setting admin key failed; sw=%04x\n", sw);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
return err;
}
/* Handle the SETATTR operation. All arguments are already basically
* checked. */
static gpg_error_t
do_setattr (app_t app, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
static struct {
const char *name;
unsigned short tag;
unsigned short flush_tag; /* The tag which needs to be flushed or 0. */
int special; /* Special mode to use for thus NAME. */
} table[] = {
/* Authenticate using the PIV Card Application Administration Key
* (0x0B). Note that Yubico calls this key the "management key"
* which we don't do because that term is too similar to "Cert
* Management Key" (0x9D). */
{ "AUTH-ADM-KEY", 0x0000, 0x0000, 1 },
{ "SET-ADM-KEY", 0x0000, 0x0000, 2 }
};
int idx;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
return gpg_error (GPG_ERR_INV_NAME);
/* Flush the cache before writing it, so that the next get operation
* will reread the data from the card and thus get synced in case of
* errors (e.g. data truncated by the card). */
if (table[idx].tag)
flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag
/* */ : table[idx].tag);
switch (table[idx].special)
{
case 1:
err = auth_adm_key (app, value, valuelen);
break;
case 2:
err = set_adm_key (app, value, valuelen);
break;
default:
err = gpg_error (GPG_ERR_BUG);
break;
}
return err;
}
/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying
* the key. This is used by the LEARN command. */
static gpg_error_t
send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
int only_keypair)
{
gpg_error_t err = 0;
char *keygripstr = NULL;
int got_cert;
char idbuf[50];
const char *usage;
err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert);
if (err)
goto leave;
usage = dobj->usage? dobj->usage : "";
snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
idbuf, strlen (idbuf),
usage, strlen (usage),
NULL, (size_t)0);
if (!only_keypair && got_cert)
{
/* All certificates are of type 100 (Regular X.509 Cert). */
send_status_info (ctrl, "CERTINFO",
"100", 3,
idbuf, strlen (idbuf),
NULL, (size_t)0);
}
leave:
xfree (keygripstr);
return err;
}
/* Handle the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
int i;
(void)flags;
do_getattr (app, ctrl, "CHV-USAGE");
do_getattr (app, ctrl, "CHV-STATUS");
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].keypair)
send_keypair_and_cert_info (app, ctrl, data_objects + i,
!!(flags & APP_LEARN_FLAG_KEYPAIRINFO));
return 0;
}
/* Core of do_readcert which fetches the certificate based on the
* given tag and returns it in a freshly allocated buffer stored at
* R_CERT and the length of the certificate stored at R_CERTLEN. If
* on success a non-zero value is stored at R_MECHANISM, the returned
* data is not a certificate but a public key (in the format used by the
* container '7f49'. */
static gpg_error_t
readcert_by_tag (app_t app, unsigned int tag,
unsigned char **r_cert, size_t *r_certlen, int *r_mechanism)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
const unsigned char *s, *s2;
size_t n, n2;
*r_cert = NULL;
*r_certlen = 0;
*r_mechanism = 0;
relptr = get_one_do (app, tag, &buffer, &buflen, NULL);
if (!relptr || !buflen)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
s = find_tlv (buffer, buflen, 0x71, &n);
if (!s)
{
/* No certificate; check whether a public key has been stored
* using our own scheme. */
s = find_tlv (buffer, buflen, 0x7f49, &n);
if (!s || !n)
{
log_error ("piv: No public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
s2 = find_tlv (buffer, buflen, 0x80, &n2);
if (!s2 || n2 != 1 || !*s2)
{
log_error ("piv: No mechanism for public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
*r_mechanism = *s2;
}
else
{
if (n != 1)
{
log_error ("piv: invalid CertInfo in 0x%X\n", tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
if (*s == 0x01)
{
log_error ("piv: gzip compression not yet supported (tag 0x%X)\n",
tag);
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto leave;
}
if (*s)
{
log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
/* Note: We don't check that the LRC octet has a length of zero
* as required by the specs. */
/* Get the cert from the container. */
s = find_tlv (buffer, buflen, 0x70, &n);
if (!s || !n)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* The next is common for certificate and public key. */
if (!(*r_cert = xtrymalloc (n)))
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_cert, s, n);
*r_certlen = n;
err = 0;
leave:
xfree (relptr);
return err;
}
/* Get the keygrip in hex format of a key from the certificate stored
* at TAG. Caller must free the string at R_KEYGRIPSTR. */
static gpg_error_t
get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *r_got_cert)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
ksba_cert_t cert = NULL;
unsigned char grip[KEYGRIP_LEN];
*r_got_cert = 0;
*r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1);
if (!r_keygripstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We need to get the public key from the certificate. */
err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism) /* Compute keygrip from public key. */
{
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
log_error ("piv: error computing keygrip\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
bin2hex (grip, sizeof grip, *r_keygripstr);
}
else /* Compute keygrip from certificate. */
{
*r_got_cert = 0;
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
goto leave;
err = app_help_get_keygrip_string (cert, *r_keygripstr);
}
leave:
gcry_sexp_release (s_pkey);
ksba_cert_release (cert);
xfree (certbuf);
if (err)
{
xfree (*r_keygripstr);
*r_keygripstr = NULL;
}
return err;
}
/* Locate the data object from the given KEYREF. The KEYREF may also
* be the corresponding OID of the key object. Returns the data
* object or NULL if not found. */
static data_object_t
find_dobj_by_keyref (app_t app, const char *keyref)
{
int i;
(void)app;
if (!ascii_strncasecmp (keyref, "PIV.", 4)) /* Standard keyref */
{
keyref += 4;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !ascii_strcasecmp (keyref, data_objects[i].keyref))
{
return data_objects + i;
}
}
else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) /* OID */
{
keyref += 19;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !strcmp (keyref, data_objects[i].oidsuffix))
{
return data_objects + i;
}
}
else if (strlen (keyref) == 40) /* A keygrip */
{
char *keygripstr = NULL;
int tag, dummy_got_cert;
for (i=0; (tag=data_objects[i].tag); i++)
{
if (!data_objects[i].keypair)
continue;
xfree (keygripstr);
if (get_keygrip_by_tag (app, tag, &keygripstr, &dummy_got_cert))
continue;
if (!strcmp (keygripstr, keyref))
{
xfree (keygripstr);
return data_objects + i;
}
}
xfree (keygripstr);
}
return NULL;
}
/* Return the keyref from DOBJ as an integer. If it does not exist,
* return -1. */
static int
keyref_from_dobj (data_object_t dobj)
{
if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1))
return -1;
return xtoi_2 (dobj->keyref);
}
/* Read a certificate from the card and returned in a freshly
* allocated buffer stored at R_CERT and the length of the certificate
* stored at R_CERTLEN. CERTID is either the OID of the cert's
* container or of the form "PIV.<two_hexdigit_keyref>" */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
data_object_t dobj;
int mechanism;
*r_cert = NULL;
*r_certlen = 0;
/* Hack to read a Yubikey attestation certificate. */
if (app->app_local->flags.yubikey
&& strlen (certid) == 11
&& !ascii_strncasecmp (certid, "PIV.ATST.", 9)
&& hexdigitp (certid+9) && hexdigitp (certid+10))
{
unsigned char apdu[4];
unsigned char *result;
size_t resultlen;
apdu[0] = 0;
apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */
apdu[2] = xtoi_2 (certid+9);
apdu[3] = 0;
err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 1,
NULL, &result, &resultlen);
if (!err)
{
*r_cert = result;
*r_certlen = resultlen;
}
return err;
}
dobj = find_dobj_by_keyref (app, certid);
if (!dobj)
return gpg_error (GPG_ERR_INV_ID);
err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism);
if (!err && mechanism)
{
/* Well, no certificate but a public key - we don't want it. */
xfree (*r_cert);
*r_cert = NULL;
*r_certlen = 0;
err = gpg_error (GPG_ERR_NOT_FOUND);
}
return err;
}
/* Return a public key in a freshly allocated buffer. This will only
* work for a freshly generated key as long as no reset of the
* application has been performed. This is because we return a cached
* result from key generation. If no cached result is available, the
* error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher
* layer can then get the key by reading the matching certificate.
* On success a canonical encoded s-expression with the public key is
* stored at (R_PK,R_PKLEN); the caller must release that buffer. On
* error R_PK and R_PKLEN are not changed and an error code is
* returned.
*/
static gpg_error_t
do_readkey (app_t app, ctrl_t ctrl, const char *keyrefstr, unsigned int flags,
unsigned char **r_pk, size_t *r_pklen)
{
gpg_error_t err;
data_object_t dobj;
int keyref;
unsigned char *cert = NULL;
size_t certlen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
unsigned char *pk = NULL;
size_t pklen;
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism);
if (err)
goto leave;
if (!mechanism)
{
/* We got a certificate. Extract the pubkey from it. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
{
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (err));
goto leave;
}
}
else
{
/* Convert the public key into the expected s-expression. */
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (cert, certlen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
err = make_canon_sexp (s_pkey, &pk, &pklen);
if (err)
goto leave;
}
if ((flags & APP_READKEY_FLAG_INFO))
{
char keygripstr[KEYGRIP_LEN*2+1];
char idbuf[50];
const char *usage;
err = app_help_get_keygrip_string_pk (pk, pklen, keygripstr);
if (err)
{
log_error ("app_help_get_keygrip_string_pk failed: %s\n",
gpg_strerror (err));
goto leave;
}
usage = dobj->usage? dobj->usage : "";
snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
idbuf, strlen (idbuf),
usage, strlen (usage),
NULL, (size_t)0);
}
if (r_pk && r_pklen)
{
*r_pk = pk;
pk = NULL;
*r_pklen = pklen;
}
leave:
gcry_sexp_release (s_pkey);
xfree (pk);
xfree (cert);
return err;
}
/* Given a data object DOBJ return the corresponding PIV algorithm and
* store it at R_ALGO. The algorithm is taken from the corresponding
* certificate or from a cache. */
static gpg_error_t
get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
ksba_cert_t cert = NULL;
ksba_sexp_t k_pkey = NULL;
gcry_sexp_t s_pkey = NULL;
gcry_sexp_t l1 = NULL;
char *algoname = NULL;
int algo;
size_t n;
const char *curve_name;
*r_mechanism = 0;
err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism)
{
/* A public key was found. That makes it easy. */
switch (mechanism)
{
case PIV_ALGORITHM_RSA:
case PIV_ALGORITHM_ECC_P256:
case PIV_ALGORITHM_ECC_P384:
*r_mechanism = mechanism;
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: unknown mechanism %d in public key at %s\n",
mechanism, dobj->keyref);
break;
}
goto leave;
}
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
{
log_error ("piv: failed to parse the certificate %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
xfree (certbuf);
certbuf = NULL;
k_pkey = ksba_cert_get_public_key (cert);
if (!k_pkey)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL);
err = gcry_sexp_new (&s_pkey, k_pkey, n, 0);
if (err)
goto leave;
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
if (!algoname)
{
err = gpg_error_from_syserror ();
goto leave;
}
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA:
algo = PIV_ALGORITHM_RSA;
break;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
curve_name = gcry_pk_get_curve (s_pkey, 0, NULL);
if (curve_name && !strcmp (curve_name, "NIST P-256"))
algo = PIV_ALGORITHM_ECC_P256;
else if (curve_name && !strcmp (curve_name, "NIST P-384"))
algo = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
log_error ("piv: certificate %s, curve '%s': %s\n",
dobj->keyref, curve_name, gpg_strerror (err));
goto leave;
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: certificate %s, pubkey algo '%s': %s\n",
dobj->keyref, algoname, gpg_strerror (err));
goto leave;
}
*r_mechanism = algo;
leave:
gcry_free (algoname);
gcry_sexp_release (l1);
gcry_sexp_release (s_pkey);
ksba_free (k_pkey);
xfree (certbuf);
return err;
}
/* Helper to cache the pin PINNO. If PIN is NULL the cache is cleared. */
static void
cache_pin (app_t app, ctrl_t ctrl, int pinno,
const char *pin, unsigned int pinlen)
{
char pinref[20];
if (pinno < 0)
return;
switch (app->card->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return;
}
snprintf (pinref, sizeof pinref, "%02x", pinno);
pincache_put (ctrl, app_get_slot (app), "piv", pinref, pin, pinlen);
switch (pinno)
{
case 0x00: app->app_local->pincache.maybe_00 = !!pin; break;
case 0x80: app->app_local->pincache.maybe_80 = !!pin; break;
case 0x81: app->app_local->pincache.maybe_81 = !!pin; break;
case 0x96: app->app_local->pincache.maybe_96 = !!pin; break;
case 0x97: app->app_local->pincache.maybe_97 = !!pin; break;
case 0x98: app->app_local->pincache.maybe_98 = !!pin; break;
case 0x9B: app->app_local->pincache.maybe_9B = !!pin; break;
}
}
/* If the PIN cache is available and really has a valid PIN return
* that pin at R_PIN. Returns true if that is the case; otherwise
* false. */
static int
pin_from_cache (app_t app, ctrl_t ctrl, int pinno, char **r_pin)
{
char pinref[20];
int maybe_cached;
*r_pin = NULL;
if (pinno < 0)
return 0;
switch (app->card->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return 0;
}
switch (pinno)
{
case 0x00: maybe_cached = app->app_local->pincache.maybe_00; break;
case 0x80: maybe_cached = app->app_local->pincache.maybe_80; break;
case 0x81: maybe_cached = app->app_local->pincache.maybe_81; break;
case 0x96: maybe_cached = app->app_local->pincache.maybe_96; break;
case 0x97: maybe_cached = app->app_local->pincache.maybe_97; break;
case 0x98: maybe_cached = app->app_local->pincache.maybe_98; break;
case 0x9B: maybe_cached = app->app_local->pincache.maybe_9B; break;
default: maybe_cached = 0;
}
if (!maybe_cached)
return 0;
snprintf (pinref, sizeof pinref, "%02x", pinno);
if (pincache_get (ctrl, app_get_slot (app), "piv", pinref, r_pin))
return 0;
return 1;
}
/* Return an allocated string to be used as prompt. Returns NULL on
* malloc error. */
static char *
make_prompt (app_t app, int remaining, const char *firstline)
{
char *serial, *tmpbuf, *result;
serial = get_dispserialno (app, 0);
if (!serial)
return NULL;
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
"Unknown", /* Fixme */
"");
xfree (serial);
/* Append a "remaining attempts" info if needed. */
if (remaining != -1 && remaining < 3)
{
char *rembuf;
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (rembuf)
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
"%0A%0A", rembuf, NULL);
xfree (rembuf);
}
else
tmpbuf = NULL;
xfree (result);
result = tmpbuf;
}
else
{
tmpbuf = strconcat (firstline, "%0A%0A", result, NULL);
xfree (result);
result = tmpbuf;
}
return result;
}
/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On
* success the result is stored at (R_PIN,R_PINLEN). */
static gpg_error_t
ask_and_prepare_chv (app_t app, ctrl_t ctrl,
int keyref, int ask_new, int remaining, int no_cache,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg, char **r_pin, unsigned int *r_pinlen,
unsigned int *r_unpaddedpinlen)
{
gpg_error_t err;
const char *label;
char *prompt;
char *pinvalue = NULL;
unsigned int pinlen;
char *pinbuffer = NULL;
int minlen, maxlen, padding, onlydigits;
*r_pin = NULL;
*r_pinlen = 0;
if (r_unpaddedpinlen)
*r_unpaddedpinlen = 0;
if (ask_new)
remaining = -1;
if (remaining != -1)
log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining);
switch (keyref)
{
case 0x00:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new Global-PIN")
/**/ : _("||Please enter the Global-PIN of your PIV card"));
break;
case 0x80:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new PIN")
/**/ : _("||Please enter the PIN of your PIV card"));
break;
case 0x81:
minlen = 8;
maxlen = 8;
padding = 0;
onlydigits = 0;
label = (ask_new? _("|N|Please enter the new Unblocking Key")
/**/ :_("||Please enter the Unblocking Key of your PIV card"));
break;
case 0x96:
case 0x97:
case 0x98:
case 0x9B:
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
default:
return gpg_error (GPG_ERR_INV_ID);
}
/* Ask for the PIN. */
if (!no_cache && remaining >= 3
&& pin_from_cache (app, ctrl, keyref, &pinvalue))
err = 0;
else
{
prompt = make_prompt (app, remaining, label);
err = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
}
if (err)
{
log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err));
return err;
}
pinlen = pinvalue? strlen (pinvalue) : 0;
if (pinlen < minlen)
{
log_error (_("PIN for is too short; minimum length is %d\n"), minlen);
if (pinvalue)
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (pinlen > maxlen)
{
log_error (_("PIN for is too long; maximum length is %d\n"), maxlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (onlydigits && strspn (pinvalue, "0123456789") != pinlen)
{
log_error (_("PIN has invalid characters; only digits are allowed\n"));
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
pinbuffer = xtrymalloc_secure (maxlen);
if (!pinbuffer)
{
err = gpg_error_from_syserror ();
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return err;
}
memcpy (pinbuffer, pinvalue, pinlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
if (r_unpaddedpinlen)
*r_unpaddedpinlen = pinlen;
if (padding)
{
memset (pinbuffer + pinlen, 0xff, maxlen - pinlen);
pinlen = maxlen;
}
*r_pin = pinbuffer;
*r_pinlen = pinlen;
return 0;
}
/* Verify the card holder verification identified by KEYREF. This is
- * either the Appication PIN or the Global PIN. If FORCE is true a
+ * either the Application PIN or the Global PIN. If FORCE is true a
* verification is always done. */
static gpg_error_t
verify_chv (app_t app, ctrl_t ctrl, int keyref, int force,
gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
{
gpg_error_t err;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *pin = NULL;
unsigned int pinlen, unpaddedpinlen;
/* First check whether a verify is at all needed. This is done with
* P1 being 0 and no Lc and command data send. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
{
if (!force) /* No need to verification. */
return 0; /* All fine. */
remaining = -1;
}
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
err = ask_and_prepare_chv (app, ctrl, keyref, 0, remaining, force,
pincb, pincb_arg,
&pin, &pinlen, &unpaddedpinlen);
if (err)
return err;
err = iso7816_verify (app_get_slot (app), keyref, pin, pinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
cache_pin (app, ctrl, keyref, NULL, 0);
}
else
cache_pin (app, ctrl, keyref, pin, unpaddedpinlen);
wipememory (pin, pinlen);
xfree (pin);
return err;
}
/* Handle the PASSWD command. Valid values for PWIDSTR are
* key references related to PINs; in particular:
* PIV.00 - The Global PIN
* PIV.80 - The Application PIN
* PIV.81 - The PIN Unblocking key
* The supported flags are:
* APP_CHANGE_FLAG_CLEAR Clear the PIN verification state.
* APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only
* allowed with PIV.80.
*/
static gpg_error_t
do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int keyref, targetkeyref;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *oldpin = NULL;
unsigned int oldpinlen;
char *newpin = NULL;
unsigned int newpinlen;
(void)ctrl;
/* Check for unknown flags. */
if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET)))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
goto leave;
}
/* Parse the keyref. */
targetkeyref = keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
cache_pin (app, ctrl, keyref, NULL, 0);
/* First see whether the special --clear mode has been requested. */
if ((flags & APP_CHANGE_FLAG_CLEAR))
{
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0xff;
apdu[3] = keyref;
err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0,
NULL, NULL, NULL);
goto leave;
}
/* Prepare reset mode. */
if ((flags & APP_CHANGE_FLAG_RESET))
{
if (keyref == 0x81)
{
err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */
goto leave;
}
/* Set the keyref to the PUK and keep the TARGETKEYREF. */
keyref = 0x81;
}
/* Get the remaining tries count. This is done by using the check
* for verified state feature. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
remaining = -1; /* Already verified, thus full number of tries. */
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
/* Ask for the old pin or puk. */
err = ask_and_prepare_chv (app, ctrl, keyref, 0, remaining, 0,
pincb, pincb_arg,
&oldpin, &oldpinlen, NULL);
if (err)
return err;
/* Verify the old pin so that we don't prompt for the new pin if the
* old is wrong. This is not possible for the PUK, though. */
if (keyref != 0x81)
{
err = iso7816_verify (app_get_slot (app), keyref, oldpin, oldpinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
goto leave;
}
}
/* Ask for the new pin. */
err = ask_and_prepare_chv (app, ctrl, targetkeyref, 1, -1, 0,
pincb, pincb_arg,
&newpin, &newpinlen, NULL);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *buf = xtrymalloc_secure (oldpinlen + newpinlen);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (buf, oldpin, oldpinlen);
memcpy (buf+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app_get_slot (app),
targetkeyref,
buf, oldpinlen+newpinlen);
xfree (buf);
if (err)
log_error ("resetting CHV %02X using CHV %02X failed: %s\n",
targetkeyref, keyref, gpg_strerror (err));
}
else
{
err = iso7816_change_reference_data (app_get_slot (app), keyref,
oldpin, oldpinlen,
newpin, newpinlen);
if (err)
log_error ("CHV %02X changing PIN failed: %s\n",
keyref, gpg_strerror (err));
}
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation for the PIN specified by PWIDSTR.
* For valid values see do_change_chv. */
static gpg_error_t
do_check_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int keyref;
(void)ctrl;
keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
return gpg_error (GPG_ERR_INV_ID);
return verify_chv (app, ctrl, keyref, 0, pincb, pincb_arg);
}
/* Compute a digital signature using the GENERAL AUTHENTICATE command
* on INDATA which is expected to be the raw message digest. The
* KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The
* result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is
* stored there and an error code returned. For ECDSA the result is
* the simple concatenation of R and S without any DER encoding. R
* and S are left extended with zeroes to make sure they have an equal
* length. If HASHALGO is not zero, the function prepends the hash's
* OID to the indata or checks that it is consistent.
*/
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char oidbuf[64];
size_t oidbuflen;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
int force_verify;
(void)ctrl;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
/* According to table 4b of SP800-73-4 the signing key always
* requires a verify. */
switch (keyref)
{
case 0x9c: force_verify = 1; break;
default: force_verify = 0; break;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
/* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA
* we need to add the padding and possible also the ASN.1 prefix. */
if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
{
int need_algo, need_digestlen;
if (mechanism == PIV_ALGORITHM_ECC_P256)
{
need_algo = GCRY_MD_SHA256;
need_digestlen = 32;
}
else
{
need_algo = GCRY_MD_SHA384;
need_digestlen = 48;
}
if (hashalgo && hashalgo != need_algo)
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("piv: hash algo %d does not match mechanism %d\n",
need_algo, mechanism);
goto leave;
}
if (indatalen > need_digestlen)
{
oidbuflen = sizeof oidbuf;
err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
if (err)
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: no OID for hash algo %d\n", need_algo);
goto leave;
}
if (indatalen != oidbuflen + need_digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with mechanism %d\n",
mechanism);
goto leave;
}
indata += oidbuflen;
indatalen -= oidbuflen;
}
}
else if (mechanism == PIV_ALGORITHM_RSA)
{
/* PIV requires 2048 bit RSA. */
unsigned int framelen = 2048 / 8;
unsigned char *frame;
int i;
oidbuflen = sizeof oidbuf;
if (!hashalgo)
{
/* We assume that indata already has the required
* digestinfo; thus merely prepend the padding below. */
}
else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
{
log_debug ("piv: no OID for hash algo %d\n", hashalgo);
goto leave;
}
else
{
unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
if (indatalen == digestlen)
{
/* Plain hash in INDATA; prepend the digestinfo. */
indata_buffer = xtrymalloc (oidbuflen + indatalen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, oidbuf, oidbuflen);
memcpy (indata_buffer+oidbuflen, indata, indatalen);
indata = indata_buffer;
indatalen = oidbuflen + indatalen;
}
else if (indatalen == oidbuflen + digestlen
&& !memcmp (indata, oidbuf, oidbuflen))
; /* Correct prefix. */
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with RSA and hash %d\n",
hashalgo);
goto leave;
}
}
/* Now prepend the pkcs#v1.5 padding. We require at least 8
* byte of padding and 3 extra bytes for the prefix and the
* delimiting nul. */
if (!indatalen || indatalen + 8 + 4 > framelen)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n",
8*framelen);
goto leave;
}
frame = xtrymalloc (framelen);
if (!frame)
{
err = gpg_error_from_syserror ();
goto leave;
}
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = framelen - indatalen - 3 ;
memset (frame+n, 0xff, i);
n += i;
frame[n++] = 0; /* Delimiter. */
memcpy (frame+n, indata, indatalen);
n += indatalen;
log_assert (n == framelen);
/* And now put it into the indata_buffer. */
xfree (indata_buffer);
indata_buffer = frame;
indata = indata_buffer;
indatalen = framelen;
}
else
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
goto leave;
}
/* Now verify the Application PIN. */
err = verify_chv (app, ctrl, 0x80, force_verify, pincb, pincb_arg);
if (err)
return err;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
(int)0x81, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
if (mechanism == PIV_ALGORITHM_RSA)
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else /* ECC */
{
const unsigned char *rval, *sval;
size_t rlen, rlenx, slen, slenx, resultlen;
char *result;
/* The result of an ECDSA signature is
* SEQUENCE { r INTEGER, s INTEGER }
* We re-pack that by concatenating R and S and making sure
* that both have the same length. We simplify parsing by
* using find_tlv and not a proper DER parser. */
s = find_tlv (s, n, 0x30, &n);
if (!s)
goto bad_der;
rval = find_tlv (s, n, 0x02, &rlen);
if (!rval)
goto bad_der;
log_assert (n >= (rval-s)+rlen);
sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
if (!rval)
goto bad_der;
rlenx = slenx = 0;
if (rlen > slen)
slenx = rlen - slen;
else if (slen > rlen)
rlenx = slen - rlen;
resultlen = rlen + rlenx + slen + slenx;
result = xtrycalloc (1, resultlen);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (result + rlenx, rval, rlen);
memcpy (result + rlenx + rlen + slenx, sval, slen);
xfree (outdata);
outdata = result;
outdatalen = resultlen;
}
}
else
{
bad_der:
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* AUTH for PIV cards is actually the same as SIGN. The difference
* between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding
* for RSA has already been done (digestInfo part w/o the padding)
* whereas SIGN may accept a plain digest and does the padding if
* needed. This is also the reason why SIGN takes a hashalgo. */
static gpg_error_t
do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
return do_sign (app, ctrl, keyidstr, 0, pincb, pincb_arg, indata, indatalen,
r_outdata, r_outdatalen);
}
/* Decrypt the data in (INDATA,INDATALEN) and on success store the
* mallocated result at (R_OUTDATA,R_OUTDATALEN). */
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen,
unsigned int *r_info)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned int framelen;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
(void)ctrl;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E)
{
/* Signing only reference. We only allow '9D' and the retired
* cert key management DOs. */
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
switch (mechanism)
{
case PIV_ALGORITHM_ECC_P256:
framelen = 1+32+32;
break;
case PIV_ALGORITHM_ECC_P384:
framelen = 1+48+48;
break;
case PIV_ALGORITHM_RSA:
framelen = 2048 / 8;
break;
default:
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism);
goto leave;
}
/* Check that the ciphertext has the right length; due to internal
* convey mechanism using MPIs leading zero bytes might have been
* lost. Adjust for this. Unfortunately the ciphertext might have
* also been prefixed with a leading zero to make it a positive
* number; that may be a too long frame and we need to adjust for
- * this too. Note that for ECC thoses fixes are not reqquired
+ * this too. Note that for ECC those fixes are not reqquired
* because the first octet is always '04' to indicate an
* uncompressed point. */
if (indatalen > framelen)
{
if (mechanism == PIV_ALGORITHM_RSA
&& indatalen == framelen + 1 && !*indata)
{
indata_buffer = xtrycalloc (1, framelen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, indata+1, framelen);
indata = indata_buffer;
indatalen = framelen;
}
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input of %zu octets too large for mechanism %d\n",
indatalen, mechanism);
goto leave;
}
}
if (indatalen < framelen)
{
indata_buffer = xtrycalloc (1, framelen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer+(framelen-indatalen), indata, indatalen);
indata = indata_buffer;
indatalen = framelen;
}
/* Now verify the Application PIN. */
err = verify_chv (app, ctrl, 0x80, 0, pincb, pincb_arg);
if (err)
return err;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
mechanism == PIV_ALGORITHM_RSA?
(int)0x81 : (int)0x85, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
*r_info = 0;
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* Check whether a key for DOBJ already exists. We detect this by
* reading the certificate described by DOBJ. If FORCE is TRUE a
* diagnositic will be printed but no error returned if the key
* already exists. The flag GENERATING is used to select a
* diagnositic. */
static gpg_error_t
does_key_exist (app_t app, data_object_t dobj, int generating, int force)
{
void *relptr;
unsigned char *buffer;
size_t buflen;
int found;
relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL);
found = (relptr && buflen);
xfree (relptr);
if (found && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
if (found)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_rsa (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
unsigned char *rsa_dpm1 = NULL;
unsigned char *rsa_dqm1 = NULL;
unsigned char *rsa_qinv = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!rsa_n || !rsa_e || !rsa_p || !rsa_q)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Fixme: Shall we check whether n == pq ? */
if (opt.verbose)
log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len);
/* Compute the dp, dq and u components. */
{
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_qinv = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp);
gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_dpm1);
gcry_mpi_release (mpi_dqm1);
gcry_mpi_release (mpi_qinv);
gcry_mpi_release (mpi_tmp);
}
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x01, (size_t)rsa_p_len, rsa_p,
(int)0x02, (size_t)rsa_q_len, rsa_q,
(int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1,
(int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1,
(int)0x05, (size_t)rsa_qinv_len, rsa_qinv,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
PIV_ALGORITHM_RSA, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x81, (size_t)rsa_n_len, rsa_n,
(int)0x82, (size_t)rsa_e_len, rsa_e,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = PIV_ALGORITHM_RSA;
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (rsa_dpm1);
xfree (rsa_dqm1);
xfree (rsa_qinv);
xfree (apdudata);
return err;
}
/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_ecc (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
int mechanism = 0;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *name;
const char *xname;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
name = xtrymalloc (toklen+1);
if (!name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (name, tok, toklen);
name[toklen] = 0;
/* Canonicalize the curve name. We use the openpgp
* functions here because Libgcrypt has no generic curve
* alias lookup feature and the PIV supported curves are
* also supported by OpenPGP. */
xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL, NULL),
0);
xfree (name);
if (xname && !strcmp (xname, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (xname && !strcmp (xname, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
}
else if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!mechanism || !ecc_q || !ecc_d)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x06, (size_t)ecc_d_len, ecc_d,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
mechanism, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x86, (size_t)ecc_q_len, ecc_q,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = mechanism;
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (apdudata);
return err;
}
/* Write a key to a slot. This command requires proprietary
* extensions of the PIV specification and is thus only implemnted for
* supported card types. The input is a canonical encoded
* S-expression with the secret key in KEYDATA and its length (for
* assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2
* hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are
* not used for PIV cards.
*
* Supported FLAGS are:
* APP_WRITEKEY_FLAG_FORCE Overwrite existing key.
*/
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyrefstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = !!(flags & APP_WRITEKEY_FLAG_FORCE);
data_object_t dobj;
int keyref;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!app->app_local->flags.yubikey)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Check keyref and test whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 0, force);
if (err)
goto leave;
/* Parse the S-expression with the key. */
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
/* First clear an existing key. We do this by writing an empty 7f49
* tag. This will return GPG_ERR_NO_PUBKEY on a later read. */
flush_cached_data (app, dobj->tag);
err = put_data (app_get_slot (app), dobj->tag,
(int)0x7f49, (size_t)0, "",
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to clear the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
/* Divert to the algo specific implementation. */
if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
err = writekey_rsa (app, dobj, keyref, buf, buflen, depth);
else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
err = writekey_ecc (app, dobj, keyref, buf, buflen, depth);
else
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
}
leave:
return err;
}
/* Parse an RSA response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. */
static gpg_error_t
genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
unsigned char *mbuf = NULL;
unsigned char *ebuf = NULL;
size_t mlen, elen;
*r_sexp = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* Strip leading zeroes */
;
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Parse an ECC response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. MECHANISM specifies the
* curve. */
static gpg_error_t
genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *ecc_q;
size_t ecc_qlen;
const char *curve;
*r_sexp = NULL;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (mechanism == PIV_ALGORITHM_ECC_P256)
curve = "nistp256";
else if (mechanism == PIV_ALGORITHM_ECC_P384)
curve = "nistp384";
else
{
err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */
goto leave;
}
err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))",
curve, (int)ecc_qlen, ecc_q);
leave:
return err;
}
/* Create a new keypair for KEYREF. If KEYTYPE is NULL a default
* keytype is selected, else it may be one of the strings:
* "rsa2048", "nistp256, or "nistp384".
*
* Supported FLAGS are:
* APP_GENKEY_FLAG_FORCE Overwrite existing key.
*
* Note that CREATETIME is not used for PIV cards.
*
* Because there seems to be no way to read the public key we need to
* retrieve it from a certificate. The GnuPG system however requires
* the use of app_readkey to fetch the public key from the card to
* create the certificate; to support this we temporary store the
* generated public key in the local context for use by app_readkey.
*/
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *buffer = NULL;
size_t buflen;
int force = !!(flags & APP_GENKEY_FLAG_FORCE);
int mechanism;
time_t start_at;
int keyref;
unsigned char tmpl[5];
size_t tmpllen;
const unsigned char *keydata;
size_t keydatalen;
(void)ctrl;
(void)createtime;
(void)pincb;
(void)pincb_arg;
if (!keytype)
keytype = "rsa2048";
if (!strcmp (keytype, "rsa2048"))
mechanism = PIV_ALGORITHM_RSA;
else if (!strcmp (keytype, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (!strcmp (keytype, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* We flush the cache to increase the I/O traffic before a key
* generation. This _might_ help the card to gather more entropy
* and is anyway a prerequisite for does_key_exist. */
flush_cached_data (app, 0);
/* Check whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 1, force);
if (err)
goto leave;
/* Create the key. */
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
tmpl[0] = 0xac;
tmpl[1] = 3;
tmpl[2] = 0x80;
tmpl[3] = 1;
tmpl[4] = mechanism;
tmpllen = 5;
err = iso7816_generate_keypair (app_get_slot (app), 0, 0, keyref,
tmpl, tmpllen, 0, &buffer, &buflen);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("generating key failed\n"));
return err;
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
/* Parse the result and store it as an s-expression in a dedicated
* cache for later retrieval by app_readkey. */
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata || !keydatalen)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
tmpl[0] = mechanism;
flush_cached_data (app, dobj->tag);
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)keydatalen, keydata,
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to write key to the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
leave:
xfree (buffer);
return err;
}
/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR.
* CERTREFSTR is either the OID of the certificate's container data
* object or of the form "PIV.<two_hexdigit_keyref>". */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certrefstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *pk = NULL;
unsigned char *orig_pk = NULL;
size_t pklen, orig_pklen;
(void)ctrl;
(void)pincb; /* Not used; instead authentication is needed. */
(void)pincb_arg;
if (!certlen)
return gpg_error (GPG_ERR_INV_CERT_OBJ);
dobj = find_dobj_by_keyref (app, certrefstr);
if (!dobj || !*dobj->keyref)
return gpg_error (GPG_ERR_INV_ID);
flush_cached_data (app, dobj->tag);
/* Check that the public key parameters from the certificate match
* an already stored key. Note that we do not allow writing a
* certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or
* if there is a problem reading the public key from the certificate
* GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only
* way to detect whether a key exists is by trying to use that
* key. */
err = do_readkey (app, ctrl, certrefstr, 0, &orig_pk, &orig_pklen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */
goto leave;
}
/* Compare pubkeys. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
goto leave; /* No public key in new certificate. */
if (orig_pklen != pklen || memcmp (orig_pk, pk, pklen))
{
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
err = put_data (app_get_slot (app), dobj->tag,
(int)0x70, (size_t)certlen, cert,/* Certificate */
(int)0x71, (size_t)1, "", /* No compress */
(int)0xfe, (size_t)0, "", /* Empty LRC. */
(int)0, (size_t)0, NULL);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
if (err)
log_error ("piv: failed to write cert to %s: %s\n",
dobj->keyref, gpg_strerror (err));
leave:
xfree (pk);
xfree (orig_pk);
return err;
}
/* Process the various keygrip based info requests. */
static gpg_error_t
do_with_keygrip (app_t app, ctrl_t ctrl, int action,
const char *want_keygripstr, int capability)
{
gpg_error_t err;
char *keygripstr = NULL;
char *serialno = NULL;
char idbuf[20];
int data = 0;
int i, tag, dummy_got_cert;
/* First a quick check for valid parameters. */
switch (action)
{
case KEYGRIP_ACTION_LOOKUP:
if (!want_keygripstr)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
break;
case KEYGRIP_ACTION_SEND_DATA:
data = 1;
break;
case KEYGRIP_ACTION_WRITE_STATUS:
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Allocate the s/n string if needed. */
if (action != KEYGRIP_ACTION_LOOKUP)
{
serialno = app_get_serialno (app);
if (!serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
for (i = 0; (tag = data_objects[i].tag); i++)
{
if (!data_objects[i].keypair)
continue;
xfree (keygripstr);
if (get_keygrip_by_tag (app, tag, &keygripstr, &dummy_got_cert))
continue;
if (action == KEYGRIP_ACTION_LOOKUP)
{
if (!strcmp (keygripstr, want_keygripstr))
{
err = 0; /* Found */
goto leave;
}
}
else if (!want_keygripstr || !strcmp (keygripstr, want_keygripstr))
{
if (capability == 1)
{
if (strcmp (data_objects[i].keyref, "9C"))
continue;
}
if (capability == 2)
{
if (strcmp (data_objects[i].keyref, "9D"))
continue;
}
if (capability == 3)
{
if (strcmp (data_objects[i].keyref, "9A"))
continue;
}
snprintf (idbuf, sizeof idbuf, "PIV.%s", data_objects[i].keyref);
send_keyinfo (ctrl, data, keygripstr, serialno, idbuf);
if (want_keygripstr)
{
err = 0; /* Found */
goto leave;
}
}
}
/* Return an error so that the dispatcher keeps on looping over the
* other applications. For clarity we use a different error code
* when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR
* is not NULL. */
if (!want_keygripstr)
err = gpg_error (GPG_ERR_TRUE);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
leave:
xfree (keygripstr);
xfree (serialno);
return err;
}
/* Prepare a reselect of another application. This is used by cards
* which support on-the-fly switching between applications. The
* function is called to give us a chance to save state for a future
* reselect of us again. */
static gpg_error_t
do_prep_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)app;
(void)ctrl;
err = 0;
return err;
}
/* Reselect the application. This is used by cards which support
* on-the-fly switching between applications. */
static gpg_error_t
do_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)ctrl;
/* An extra check which should not be necessary because the caller
* should have made sure that a re-select is only called for
* appropriate cards. */
if (!app->app_local->flags.yubikey)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
err = iso7816_select_application (app_get_slot (app),
piv_aid, sizeof piv_aid, 0x0001);
return err;
}
/* Select the PIV application on the card in SLOT. This function must
* be used before any other PIV application functions. */
gpg_error_t
app_select_piv (app_t app)
{
int slot = app_get_slot (app);
gpg_error_t err;
unsigned char *apt = NULL;
size_t aptlen;
const unsigned char *s;
size_t n;
/* Note that we select using the AID without the 2 octet version
* number. This allows for better reporting of future specs. We
* need to use the use-zero-for-P2-flag. */
err = iso7816_select_application_ext (slot, piv_aid, sizeof piv_aid, 0x0001,
&apt, &aptlen);
if (err)
goto leave;
app->apptype = APPTYPE_PIV;
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* Check the Application Property Template. */
if (opt.verbose)
{
/* We use a separate log_info to avoid the "DBG:" prefix. */
log_info ("piv: APT=");
log_printhex (apt, aptlen, "");
}
s = find_tlv (apt, aptlen, 0x4F, &n);
if (!s || n != 6 || memcmp (s, piv_aid+5, 4))
{
/* The PIX does not match. */
log_error ("piv: missing or invalid DO 0x4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (s[4] != 1 || s[5] != 0)
{
log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]);
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->appversion = ((s[4] << 8) | s[5]);
s = find_tlv (apt, aptlen, 0x79, &n);
if (!s || n < 7)
{
log_error ("piv: missing or invalid DO 0x79 in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
s = find_tlv (s, n, 0x4F, &n);
if (!s || n != 5 || memcmp (s, piv_aid, 5))
{
/* The RID does not match. */
log_error ("piv: missing or invalid DO 0x79.4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (app->card->cardtype == CARDTYPE_YUBIKEY)
app->app_local->flags.yubikey = 1;
/* FIXME: Parse the optional and conditional DOs in the APT. */
if (opt.verbose)
dump_all_do (slot);
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = do_prep_reselect;
app->fnc.reselect = do_reselect;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_chv;
app->fnc.check_pin = do_check_chv;
app->fnc.with_keygrip = do_with_keygrip;
leave:
xfree (apt);
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app.c b/scd/app.c
index 5e42aaec9..f91650223 100644
--- a/scd/app.c
+++ b/scd/app.c
@@ -1,2281 +1,2281 @@
/* app.c - Application selection.
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <npth.h>
#include "scdaemon.h"
#include "../common/exechelp.h"
#include "iso7816.h"
#include "apdu.h"
#include "../common/tlv.h"
#include "../common/membuf.h"
/* Forward declaration of internal function. */
static gpg_error_t
select_additional_application_internal (card_t card, apptype_t req_apptype);
static gpg_error_t
send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl);
/* Lock to protect the list of cards and its associated
* applications. */
static npth_mutex_t card_list_lock;
/* A list of card contexts. A card is a collection of applications
* (described by app_t) on the same physical token. */
static card_t card_top;
/* The list of application names and their select function. If no
* specific application is selected the first available application on
* a card is selected. */
struct app_priority_list_s
{
apptype_t apptype;
char const *name;
gpg_error_t (*select_func)(app_t);
};
static struct app_priority_list_s app_priority_list[] =
{{ APPTYPE_OPENPGP , "openpgp", app_select_openpgp },
{ APPTYPE_PIV , "piv", app_select_piv },
{ APPTYPE_NKS , "nks", app_select_nks },
{ APPTYPE_P15 , "p15", app_select_p15 },
{ APPTYPE_GELDKARTE, "geldkarte", app_select_geldkarte },
{ APPTYPE_DINSIG , "dinsig", app_select_dinsig },
{ APPTYPE_SC_HSM , "sc-hsm", app_select_sc_hsm },
{ APPTYPE_NONE , NULL, NULL }
/* APPTYPE_UNDEFINED is special and not listed here. */
};
/* Map a cardtype to a string. Never returns NULL. */
const char *
strcardtype (cardtype_t t)
{
switch (t)
{
case CARDTYPE_GENERIC: return "generic";
case CARDTYPE_YUBIKEY: return "yubikey";
}
return "?";
}
/* Map an application type to a string. Never returns NULL. */
const char *
strapptype (apptype_t t)
{
int i;
for (i=0; app_priority_list[i].apptype; i++)
if (app_priority_list[i].apptype == t)
return app_priority_list[i].name;
return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none";
}
const char *
xstrapptype (app_t app)
{
return app? strapptype (app->apptype) : "[no_app]";
}
/* Return the apptype for NAME. */
static apptype_t
apptype_from_name (const char *name)
{
int i;
if (!name)
return APPTYPE_NONE;
for (i=0; app_priority_list[i].apptype; i++)
if (!ascii_strcasecmp (app_priority_list[i].name, name))
return app_priority_list[i].apptype;
if (!ascii_strcasecmp ("undefined", name))
return APPTYPE_UNDEFINED;
return APPTYPE_NONE;
}
/* Return the apptype for KEYREF. This is the first part of the
* KEYREF up to the dot. */
static apptype_t
apptype_from_keyref (const char *keyref)
{
int i;
unsigned int n;
const char *s;
if (!keyref)
return APPTYPE_NONE;
s = strchr (keyref, '.');
if (!s || s == keyref || !s[1])
return APPTYPE_NONE; /* Not a valid keyref. */
n = s - keyref;
for (i=0; app_priority_list[i].apptype; i++)
if (strlen (app_priority_list[i].name) == n
&& !ascii_strncasecmp (app_priority_list[i].name, keyref, n))
return app_priority_list[i].apptype;
return APPTYPE_NONE;
}
/* Initialization function to change the default app_priority_list.
* LIST is a list of comma or space separated strings with application
* names. Unknown names will only result in warning message.
* Application not mentioned in LIST are used in their original order
* after the given once. */
void
app_update_priority_list (const char *arg)
{
struct app_priority_list_s save;
char **names;
int i, j, idx;
names = strtokenize (arg, ", ");
if (!names)
log_fatal ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
idx = 0;
for (i=0; names[i]; i++)
{
ascii_strlwr (names[i]);
for (j=0; j < i; j++)
if (!strcmp (names[j], names[i]))
break;
if (j < i)
{
log_info ("warning: duplicate application '%s' in priority list\n",
names[i]);
continue;
}
for (j=idx; app_priority_list[j].name; j++)
if (!strcmp (names[i], app_priority_list[j].name))
break;
if (!app_priority_list[j].name)
{
log_info ("warning: unknown application '%s' in priority list\n",
names[i]);
continue;
}
save = app_priority_list[idx];
app_priority_list[idx] = app_priority_list[j];
app_priority_list[j] = save;
idx++;
}
log_assert (idx < DIM (app_priority_list));
xfree (names);
for (i=0; app_priority_list[i].name; i++)
log_info ("app priority %d: %s\n", i, app_priority_list[i].name);
}
static void
print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
{
ctrl_t ctrl = opaque;
char line[100];
if (ctrl)
{
snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot);
send_status_direct (ctrl, "PROGRESS", line);
}
}
/* Lock the CARD. This function shall be used right before calling
* any of the actual application functions to serialize access to the
* reader. We do this always even if the card is not actually used.
* This allows an actual connection to assume that it never shares a
* card (while performing one command). Returns 0 on success; only
* then the unlock_reader function must be called after returning from
* the handler. Right now we assume a that a reader has just one
* card; this may eventually need refinement. */
static gpg_error_t
lock_card (card_t card, ctrl_t ctrl)
{
if (npth_mutex_lock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to acquire CARD lock for %p: %s\n",
card, gpg_strerror (err));
return err;
}
apdu_set_progress_cb (card->slot, print_progress_line, ctrl);
apdu_set_prompt_cb (card->slot, popup_prompt, ctrl);
return 0;
}
/* Release a lock on a card. See lock_reader(). */
static void
unlock_card (card_t card)
{
apdu_set_progress_cb (card->slot, NULL, NULL);
apdu_set_prompt_cb (card->slot, NULL, NULL);
if (npth_mutex_unlock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to release CARD lock for %p: %s\n",
card, gpg_strerror (err));
}
}
/* This function may be called to print information pertaining to the
* current state of this module to the log. */
void
app_dump_state (void)
{
card_t c;
app_t a;
npth_mutex_lock (&card_list_lock);
for (c = card_top; c; c = c->next)
{
log_info ("app_dump_state: card=%p slot=%d type=%s\n",
c, c->slot, strcardtype (c->cardtype));
/* FIXME The use of log_info risks a race! */
for (a=c->app; a; a = a->next)
log_info ("app_dump_state: app=%p type='%s'\n",
a, strapptype (a->apptype));
}
npth_mutex_unlock (&card_list_lock);
}
/* Check whether the application NAME is allowed. This does not mean
we have support for it though. */
static int
is_app_allowed (const char *name)
{
strlist_t l;
for (l=opt.disabled_applications; l; l = l->next)
if (!strcmp (l->d, name))
return 0; /* no */
return 1; /* yes */
}
/* This function is mainly used by the serialno command to check for
* an application conflict which may appear if the serialno command is
* used to request a specific application and the connection has
* already done a select_application. Return values are:
* 0 - No conflict
* GPG_ERR_FALSE - Another application is in use but it is possible
* to switch to the requested application.
* Other code - Switching is not possible.
*
* If SERIALNO_BIN is not NULL a conflict is only asserted if the
* serialno of the card matches.
*/
gpg_error_t
check_application_conflict (card_t card, const char *name,
const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
apptype_t apptype;
if (!card || !name)
return 0;
if (!card->app)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); /* Should not happen. */
if (serialno_bin && card->serialno)
{
if (serialno_bin_len != card->serialnolen
|| memcmp (serialno_bin, card->serialno, card->serialnolen))
return 0; /* The card does not match the requested S/N. */
}
apptype = apptype_from_name (name);
if (card->app->apptype == apptype)
return 0;
if (card->app->apptype == APPTYPE_UNDEFINED)
return 0;
if (card->cardtype == CARDTYPE_YUBIKEY)
{
if (card->app->apptype == APPTYPE_OPENPGP)
{
/* Current app is OpenPGP. */
if (!ascii_strcasecmp (name, "piv"))
return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
}
else if (card->app->apptype == APPTYPE_PIV)
{
/* Current app is PIV. */
if (!ascii_strcasecmp (name, "openpgp"))
return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
}
}
log_info ("application '%s' in use - can't switch\n",
strapptype (card->app->apptype));
return gpg_error (GPG_ERR_CONFLICT);
}
gpg_error_t
card_reset (card_t card, ctrl_t ctrl, int send_reset)
{
gpg_error_t err = 0;
if (send_reset)
{
int sw;
lock_card (card, ctrl);
sw = apdu_reset (card->slot);
if (sw)
err = gpg_error (GPG_ERR_CARD_RESET);
card->reset_requested = 1;
unlock_card (card);
scd_kick_the_loop ();
gnupg_sleep (1);
}
else
{
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref (card);
}
return err;
}
static gpg_error_t
app_new_register (int slot, ctrl_t ctrl, const char *name,
int periodical_check_needed)
{
gpg_error_t err = 0;
card_t card = NULL;
app_t app = NULL;
unsigned char *result = NULL;
size_t resultlen;
int want_undefined;
int i;
/* Need to allocate a new card object */
card = xtrycalloc (1, sizeof *card);
if (!card)
{
err = gpg_error_from_syserror ();
log_info ("error allocating context: %s\n", gpg_strerror (err));
return err;
}
card->slot = slot;
card->card_status = (unsigned int)-1;
if (npth_mutex_init (&card->lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
xfree (card);
return err;
}
err = lock_card (card, ctrl);
if (err)
{
xfree (card);
return err;
}
want_undefined = (name && !strcmp (name, "undefined"));
/* Try to read the GDO file first to get a default serial number.
We skip this if the undefined application has been requested. */
if (!want_undefined)
{
err = iso7816_select_file (slot, 0x3F00, 1);
if (gpg_err_code (err) == GPG_ERR_CARD)
{
/* Might be SW==0x7D00. Let's test whether it is a Yubikey
* by selecting its manager application and then reading the
* config. */
static char const yk_aid[] =
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/
static char const otp_aid[] =
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/
unsigned char *buf;
size_t buflen;
const unsigned char *s0;
unsigned char formfactor;
size_t n;
if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid,
0x0001)
&& !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0,
NULL, &buf, &buflen))
{
card->cardtype = CARDTYPE_YUBIKEY;
if (opt.verbose)
{
log_info ("Yubico: config=");
log_printhex (buf, buflen, "");
}
/* We skip the first byte which seems to be the total
* length of the config data. */
if (buflen > 1)
{
s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */
formfactor = (s0 && n == 1)? *s0 : 0;
s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */
if (s0 && n >= 4)
{
card->serialno = xtrymalloc (3 + 1 + n);
if (card->serialno)
{
card->serialnolen = 3 + 1 + n;
card->serialno[0] = 0xff;
card->serialno[1] = 0x02;
card->serialno[2] = 0x0;
card->serialno[3] = formfactor;
memcpy (card->serialno + 4, s0, n);
/* Note that we do not clear the error
* so that no further serial number
* testing is done. After all we just
* set the serial number. */
}
}
s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */
if (s0 && n == 3)
card->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]);
else if (!s0)
{
/* No version - this is not a Yubikey 5. We now
* switch to the OTP app and take the first
- * three bytes of the reponse as version
+ * three bytes of the response as version
* number. */
xfree (buf);
buf = NULL;
if (!iso7816_select_application_ext (slot,
otp_aid, sizeof otp_aid,
1, &buf, &buflen)
&& buflen > 3)
card->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]);
}
}
xfree (buf);
}
}
if (!err)
err = iso7816_select_file (slot, 0x2F02, 0);
if (!err)
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
if (!err)
{
size_t n;
const unsigned char *p;
p = find_tlv_unchecked (result, resultlen, 0x5A, &n);
if (p)
resultlen -= (p-result);
if (p && n > resultlen && n == 0x0d && resultlen+1 == n)
{
/* The object does not fit into the buffer. This is an
invalid encoding (or the buffer is too short. However, I
have some test cards with such an invalid encoding and
therefore I use this ugly workaround to return something
I can further experiment with. */
log_info ("enabling BMI testcard workaround\n");
n--;
}
if (p && n <= resultlen)
{
/* The GDO file is pretty short, thus we simply reuse it for
storing the serial number. */
memmove (result, p, n);
card->serialno = result;
card->serialnolen = n;
err = app_munge_serialno (card);
if (err)
goto leave;
}
else
xfree (result);
result = NULL;
}
}
/* Allocate a new app object. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
card->app = app;
app->card = card;
/* Figure out the application to use. */
if (want_undefined)
{
/* We switch to the "undefined" application only if explicitly
requested. */
app->apptype = APPTYPE_UNDEFINED;
/* Clear the error so that we don't run through the application
* selection chain. */
err = 0;
}
else
{
/* For certain error codes, there is no need to try more. */
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT
|| gpg_err_code (err) == GPG_ERR_ENODEV)
goto leave;
/* Set a default error so that we run through the application
* selection chain. */
err = gpg_error (GPG_ERR_NOT_FOUND);
}
/* Find the first available app if NAME is NULL or the matching
* NAME but only if that application is also enabled. */
for (i=0; err && app_priority_list[i].name; i++)
{
if (is_app_allowed (app_priority_list[i].name)
&& (!name || !strcmp (name, app_priority_list[i].name)))
err = app_priority_list[i].select_func (app);
}
if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
if (err)
{
if (name)
log_info ("can't select application '%s': %s\n",
name, gpg_strerror (err));
else
log_info ("no supported card application found: %s\n",
gpg_strerror (err));
unlock_card (card);
xfree (app);
xfree (card);
return err;
}
card->periodical_check_needed = periodical_check_needed;
card->next = card_top;
card_top = card;
unlock_card (card);
return 0;
}
/* If called with NAME as NULL, select the best fitting application
* and return its card context; otherwise select the application with
* NAME and return its card context. Returns an error code and stores
* NULL at R_CARD if no application was found or no card is present. */
gpg_error_t
select_application (ctrl_t ctrl, const char *name, card_t *r_card,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
gpg_error_t err = 0;
card_t card, card_prev = NULL;
*r_card = NULL;
npth_mutex_lock (&card_list_lock);
if (scan || !card_top)
{
struct dev_list *l;
int new_card = 0;
/* Scan the devices to find new device(s). */
err = apdu_dev_list_start (opt.reader_port, &l);
if (err)
{
npth_mutex_unlock (&card_list_lock);
return err;
}
while (1)
{
int slot;
int periodical_check_needed_this;
slot = apdu_open_reader (l);
if (slot < 0)
break;
periodical_check_needed_this = apdu_connect (slot);
if (periodical_check_needed_this < 0)
{
/* We close a reader with no card. */
err = gpg_error (GPG_ERR_ENODEV);
}
else
{
err = app_new_register (slot, ctrl, name,
periodical_check_needed_this);
new_card++;
}
if (err)
{
pincache_put (ctrl, slot, NULL, NULL, NULL, 0);
apdu_close_reader (slot);
}
}
apdu_dev_list_finish (l);
/* If new device(s), kick the scdaemon loop. */
if (new_card)
scd_kick_the_loop ();
}
for (card = card_top; card; card = card->next)
{
lock_card (card, ctrl);
if (serialno_bin == NULL)
break;
if (card->serialnolen == serialno_bin_len
&& !memcmp (card->serialno, serialno_bin, card->serialnolen))
break;
unlock_card (card);
card_prev = card;
}
if (card)
{
err = check_application_conflict (card, name, NULL, 0);
if (!err)
ctrl->current_apptype = card->app ? card->app->apptype : APPTYPE_NONE;
else if (gpg_err_code (err) == GPG_ERR_FALSE)
{
apptype_t req_apptype = apptype_from_name (name);
if (!req_apptype)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
err = select_additional_application_internal (card, req_apptype);
if (!err)
ctrl->current_apptype = req_apptype;
}
}
if (!err)
{
/* Note: We do not use card_ref as we are already locked. */
card->ref_count++;
*r_card = card;
if (card_prev)
{
card_prev->next = card->next;
card->next = card_top;
card_top = card;
}
}
unlock_card (card);
}
else
err = gpg_error (GPG_ERR_ENODEV);
npth_mutex_unlock (&card_list_lock);
return err;
}
/* Switch the current card for the session CTRL and print a SERIALNO
* status line on success. (SERIALNO, SERIALNOLEN) is the binary s/n
* of the card to switch to. */
gpg_error_t
app_switch_current_card (ctrl_t ctrl,
const unsigned char *serialno, size_t serialnolen)
{
gpg_error_t err;
card_t card, cardtmp;
npth_mutex_lock (&card_list_lock);
if (!ctrl->card_ctx)
{
err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
goto leave;
}
if (serialno && serialnolen)
{
for (card = card_top; card; card = card->next)
{
if (card->serialnolen == serialnolen
&& !memcmp (card->serialno, serialno, card->serialnolen))
break;
}
if (!card)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Note: We do not use card_ref here because we only swap the
* context of the current session and there is no chance of a
* context switch. This also works if the card stays the same. */
cardtmp = ctrl->card_ctx;
ctrl->card_ctx = card;
card->ref_count++;
card_unref_locked (cardtmp);
}
/* Print the status line. */
err = send_serialno_and_app_status (ctrl->card_ctx, 0, ctrl);
leave:
npth_mutex_unlock (&card_list_lock);
return err;
}
static gpg_error_t
select_additional_application_internal (card_t card, apptype_t req_apptype)
{
gpg_error_t err = 0;
app_t app;
int i;
/* Check that the requested app has not yet been put onto the list. */
for (app = card->app; app; app = app->next)
if (app->apptype == req_apptype)
{
/* We already got this one. Note that in this case we don't
* make it the current one but it doesn't matter because
* maybe_switch_app will do that anyway. */
err = 0;
app = NULL;
goto leave;
}
/* Allocate a new app object. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
app->card = card;
/* Find the app and run the select. */
for (i=0; app_priority_list[i].apptype; i++)
{
if (app_priority_list[i].apptype == req_apptype
&& is_app_allowed (app_priority_list[i].name))
{
err = app_priority_list[i].select_func (app);
break;
}
}
if (!app_priority_list[i].apptype
|| (err && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE))
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
if (err)
goto leave;
/* Add this app. We make it the current one to avoid an extra
* reselect by maybe_switch_app after the select we just did. */
app->next = card->app;
card->app = app;
log_info ("added app '%s' to the card context and switched\n",
strapptype (app->apptype));
leave:
if (err)
xfree (app);
return err;
}
/* Add all possible additional applications to the card context but do
* not change the current one. This currently works only for Yubikeys. */
static gpg_error_t
select_all_additional_applications_internal (card_t card)
{
gpg_error_t err = 0;
apptype_t candidates[3];
int i, j;
if (card->cardtype == CARDTYPE_YUBIKEY)
{
candidates[0] = APPTYPE_OPENPGP;
candidates[1] = APPTYPE_PIV;
candidates[2] = APPTYPE_NONE;
}
else
{
candidates[0] = APPTYPE_NONE;
}
/* Find the app and run the select. */
for (i=0; app_priority_list[i].apptype; i++)
{
app_t app, app_r, app_prev;
for (j=0; candidates[j]; j++)
if (candidates[j] == app_priority_list[i].apptype
&& is_app_allowed (app_priority_list[i].name))
break;
if (!candidates[j])
continue;
for (app = card->app; app; app = app->next)
if (app->apptype == candidates[j])
break;
if (app)
continue; /* Already on the list of apps. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
app->card = card;
err = app_priority_list[i].select_func (app);
if (err)
{
log_error ("error selecting additional app '%s': %s - skipped\n",
strapptype (candidates[j]), gpg_strerror (err));
err = 0;
xfree (app);
}
else
{
/* Append to the list of apps. */
app_prev = card->app;
for (app_r=app_prev->next; app_r; app_prev=app_r, app_r=app_r->next)
;
app_prev->next = app;
log_info ("added app '%s' to the card context\n",
strapptype (app->apptype));
}
}
leave:
return err;
}
/* This function needs to be called with the NAME of the new
* application to be selected on CARD. On success the application is
* added to the list of the card's active applications as currently
* active application. On error no new application is allocated.
* Selecting an already selected application has no effect. */
gpg_error_t
select_additional_application (ctrl_t ctrl, const char *name)
{
gpg_error_t err = 0;
apptype_t req_apptype;
card_t card;
if (!name)
req_apptype = 0;
else
{
req_apptype = apptype_from_name (name);
if (!req_apptype)
return gpg_error (GPG_ERR_NOT_FOUND);
}
card = ctrl->card_ctx;
if (!card)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
err = lock_card (card, ctrl);
if (err)
return err;
if (req_apptype)
{
err = select_additional_application_internal (card, req_apptype);
if (!err)
{
ctrl->current_apptype = req_apptype;
log_debug ("current_apptype is set to %s\n", name);
}
}
else
{
err = select_all_additional_applications_internal (card);
}
unlock_card (card);
return err;
}
char *
get_supported_applications (void)
{
int idx;
size_t nbytes;
char *buffer, *p;
const char *s;
for (nbytes=1, idx=0; (s=app_priority_list[idx].name); idx++)
nbytes += strlen (s) + 1 + 1;
buffer = xtrymalloc (nbytes);
if (!buffer)
return NULL;
for (p=buffer, idx=0; (s=app_priority_list[idx].name); idx++)
if (is_app_allowed (s))
p = stpcpy (stpcpy (p, s), ":\n");
*p = 0;
return buffer;
}
/* Deallocate the application. */
static void
deallocate_card (card_t card)
{
card_t c, c_prev = NULL;
app_t a, anext;
for (c = card_top; c; c = c->next)
if (c == card)
{
if (c_prev == NULL)
card_top = c->next;
else
c_prev->next = c->next;
break;
}
else
c_prev = c;
if (card->ref_count)
log_error ("releasing still used card context (%d)\n", card->ref_count);
for (a = card->app; a; a = anext)
{
if (a->fnc.deinit)
{
a->fnc.deinit (a);
a->fnc.deinit = NULL;
}
anext = a->next;
xfree (a);
}
xfree (card->serialno);
unlock_card (card);
xfree (card);
}
/* Increment the reference counter of CARD. Returns CARD. */
card_t
card_ref (card_t card)
{
lock_card (card, NULL);
++card->ref_count;
unlock_card (card);
return card;
}
/* Decrement the reference counter for CARD. Note that we are using
* reference counting to track the users of the card's application and
* are deferring the actual deallocation to allow for a later reuse by
* a new connection. Using NULL for CARD is a no-op. */
void
card_unref (card_t card)
{
if (!card)
return;
/* We don't deallocate CARD here. Instead, we keep it. This is
useful so that a card does not get reset even if only one session
is using the card - this way the PIN cache and other cached data
are preserved. */
lock_card (card, NULL);
card_unref_locked (card);
unlock_card (card);
}
/* This is the same as card_unref but assumes that CARD is already
* locked. */
void
card_unref_locked (card_t card)
{
if (!card)
return;
if (!card->ref_count)
log_bug ("tried to release an already released card context\n");
--card->ref_count;
}
/* The serial number may need some cosmetics. Do it here. This
function shall only be called once after a new serial number has
been put into APP->serialno.
Prefixes we use:
FF 00 00 = For serial numbers starting with an FF
FF 01 00 = Some german p15 cards return an empty serial number so the
serial number from the EF(TokenInfo) is used instead.
FF 02 00 = Serial number from Yubikey config
FF 7F 00 = No serialno.
All other serial numbers not starting with FF are used as they are.
*/
gpg_error_t
app_munge_serialno (card_t card)
{
if (card->serialnolen && card->serialno[0] == 0xff)
{
/* The serial number starts with our special prefix. This
requires that we put our default prefix "FF0000" in front. */
unsigned char *p = xtrymalloc (card->serialnolen + 3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\0", 3);
memcpy (p+3, card->serialno, card->serialnolen);
card->serialnolen += 3;
xfree (card->serialno);
card->serialno = p;
}
else if (!card->serialnolen)
{
unsigned char *p = xtrymalloc (3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\x7f", 3);
card->serialnolen = 3;
xfree (card->serialno);
card->serialno = p;
}
return 0;
}
/* Retrieve the serial number of the card. The serial number is
returned as a malloced string (hex encoded) in SERIAL. Caller must
free SERIAL unless the function returns an error. */
char *
card_get_serialno (card_t card)
{
char *serial;
if (!card)
return NULL;
if (!card->serialnolen)
serial = xtrystrdup ("FF7F00");
else
serial = bin2hex (card->serialno, card->serialnolen, NULL);
return serial;
}
/* Same as card_get_serialno but takes an APP object. */
char *
app_get_serialno (app_t app)
{
if (!app || !app->card)
return NULL;
return card_get_serialno (app->card);
}
/* Helper to run the reselect function. */
static gpg_error_t
run_reselect (ctrl_t ctrl, card_t c, app_t a, app_t a_prev)
{
gpg_error_t err;
if (!a->fnc.reselect)
{
log_info ("slot %d, app %s: re-select not implemented\n",
c->slot, xstrapptype (a));
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
}
/* Give the current app a chance to save some state before another
* app is selected. We ignore errors here because that state saving
* (e.g. putting PINs into a cache) is a convenience feature and not
* required to always work. */
if (a_prev && a_prev->fnc.prep_reselect)
{
err = a_prev->fnc.prep_reselect (a_prev, ctrl);
if (err)
log_error ("slot %d, app %s: preparing re-select from %s failed: %s\n",
c->slot, xstrapptype (a),
xstrapptype (a_prev), gpg_strerror (err));
}
err = a->fnc.reselect (a, ctrl);
if (err)
{
log_error ("slot %d, app %s: error re-selecting: %s\n",
c->slot, xstrapptype (a), gpg_strerror (err));
return err;
}
if (DBG_APP)
log_debug ("slot %d, app %s: re-selected\n", c->slot, xstrapptype (a));
return 0;
}
/* Check that the card has been initialized and whether we need to
* switch to another application on the same card. Switching means
* that the new active app will be moved to the head of the list at
* CARD->app. This function must be called with the card lock held. */
static gpg_error_t
maybe_switch_app (ctrl_t ctrl, card_t card, const char *keyref)
{
gpg_error_t err;
app_t app;
app_t app_prev = NULL;
apptype_t apptype;
if (!card->ref_count || !card->app)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (!ctrl->current_apptype)
{
/* For whatever reasons the current apptype has not been set -
* fix that and use the current app. */
ctrl->current_apptype = card->app->apptype;
return 0;
}
if (DBG_APP)
log_debug ("slot %d: have=%s want=%s keyref=%s\n",
card->slot, strapptype (card->app->apptype),
strapptype (ctrl->current_apptype),
keyref? keyref:"[none]");
app = NULL;
if (keyref)
{
/* Switch based on the requested KEYREF. */
apptype = apptype_from_keyref (keyref);
if (apptype)
{
for (app = card->app; app; app_prev = app, app = app->next)
if (app->apptype == apptype)
break;
if (!app_prev && ctrl->current_apptype == card->app->apptype)
return 0; /* Already the first app - no need to switch. */
}
else if (strlen (keyref) == 40)
{
/* This looks like a keygrip. Iterate over all apps to find
* the corresponding app. */
for (app = card->app; app; app_prev = app, app = app->next)
if (app->fnc.with_keygrip
&& !app->fnc.with_keygrip (app, ctrl,
KEYGRIP_ACTION_LOOKUP, keyref, 0))
break;
if (!app_prev && ctrl->current_apptype == card->app->apptype)
return 0; /* Already the first app - no need to switch. */
}
}
if (!app)
{
/* Switch based on the current application of this connection or
* if a keyref based switch didn't worked. */
if (ctrl->current_apptype == card->app->apptype)
return 0; /* No need to switch. */
app_prev = card->app;
for (app = app_prev->next; app; app_prev = app, app = app->next)
if (app->apptype == ctrl->current_apptype)
break;
}
if (!app)
return gpg_error (GPG_ERR_WRONG_CARD);
err = run_reselect (ctrl, card, app, app_prev);
if (err)
return err;
/* Swap APP with the head of the app list if needed. Note that APP
* is not the head of the list. */
if (app_prev)
{
app_prev->next = app->next;
app->next = card->app;
card->app = app;
}
if (opt.verbose)
log_info ("slot %d, app %s: %s\n",
card->slot, xstrapptype (app),
app_prev? "switched":"re-selected");
ctrl->current_apptype = app->apptype;
return 0;
}
/* Helper for app_write_learn_status. */
static gpg_error_t
write_learn_status_core (card_t card, app_t app, ctrl_t ctrl,
unsigned int flags)
{
/* We do not send CARD and APPTYPE if only keypairinfo is requested. */
if (!(flags & APP_LEARN_FLAG_KEYPAIRINFO))
{
if (card && card->cardtype)
send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
if (card && card->cardversion)
send_status_printf (ctrl, "CARDVERSION", "%X", card->cardversion);
if (app->apptype)
send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype));
if (app->appversion)
send_status_printf (ctrl, "APPVERSION", "%X", app->appversion);
}
return app->fnc.learn_status (app, ctrl, flags);
}
/* Write out the application specific status lines for the LEARN
command. */
gpg_error_t
app_write_learn_status (card_t card, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err, err2, tmperr;
app_t app, last_app;
int any_reselect = 0;
if (!card)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
/* Always make sure that the current app for this connection has
* been selected and is at the top of the list. */
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.learn_status)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
err = write_learn_status_core (card, card->app, ctrl, flags);
if (!err && card->app->fnc.reselect && (flags & APP_LEARN_FLAG_MULTI))
{
/* The current app has the reselect feature so that we can
* loop over all other apps which are capable of a reselect
* and finally reselect the first app again. Note that we
* did the learn for the currently selected card above. */
app = last_app = card->app;
for (app = app->next; app && !err; app = app->next)
if (app->fnc.reselect)
{
if (last_app && last_app->fnc.prep_reselect)
{
tmperr = last_app->fnc.prep_reselect (last_app, ctrl);
if (tmperr)
log_info ("slot %d, app %s:"
" preparing re-select from %s failed: %s\n",
card->slot, xstrapptype (app),
xstrapptype (last_app),
gpg_strerror (tmperr));
}
any_reselect = 1;
err = app->fnc.reselect (app, ctrl);
if (!err)
{
last_app = app;
err = write_learn_status_core (NULL, app, ctrl, flags);
}
}
app = card->app;
if (any_reselect)
{
if (last_app && last_app->fnc.prep_reselect)
{
tmperr = last_app->fnc.prep_reselect (last_app, ctrl);
if (tmperr)
log_info ("slot %d, app %s:"
" preparing re-select from %s failed: %s\n",
card->slot, xstrapptype (app),
xstrapptype (last_app), gpg_strerror (tmperr));
}
err2 = app->fnc.reselect (app, ctrl);
if (err2)
{
log_error ("error re-selecting '%s': %s\n",
strapptype(app->apptype), gpg_strerror (err2));
if (!err)
err = err2;
}
}
}
}
unlock_card (card);
return err;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
gpg_error_t
app_readcert (card_t card, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
if (!card)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, certid)))
;
else if (!card->app->fnc.readcert)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling readcert(%s)\n",
card->slot, xstrapptype (card->app), certid);
err = card->app->fnc.readcert (card->app, certid, cert, certlen);
}
unlock_card (card);
return err;
}
/* Read the key with ID KEYID. On success a canonical encoded
* S-expression with the public key will get stored at PK and its
* length (for assertions) at PKLEN; the caller must release that
* buffer. On error NULL will be stored at PK and PKLEN and an error
* code returned. If the key is not required NULL may be passed for
- * PK; this makse send if the APP_READKEY_FLAG_INFO has also been set.
+ * PK; this makes sense if the APP_READKEY_FLAG_INFO has also been set.
*
* This function might not be supported by all applications. */
gpg_error_t
app_readkey (card_t card, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
if (pk)
*pk = NULL;
if (pklen)
*pklen = 0;
if (!card || !keyid)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keyid)))
;
else if (!card->app->fnc.readkey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling readkey(%s)\n",
card->slot, xstrapptype (card->app), keyid);
err = card->app->fnc.readkey (card->app, ctrl, keyid, flags, pk, pklen);
}
unlock_card (card);
return err;
}
/* Perform a GETATTR operation. */
gpg_error_t
app_getattr (card_t card, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!card || !name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (name && !strcmp (name, "CARDTYPE"))
{
send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
}
else if (name && !strcmp (name, "APPTYPE"))
{
send_status_direct (ctrl, "APPTYPE", strapptype (card->app->apptype));
}
else if (name && !strcmp (name, "SERIALNO"))
{
char *serial;
serial = card_get_serialno (card);
if (!serial)
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
}
else if (!card->app->fnc.getattr)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling getattr(%s)\n",
card->slot, xstrapptype (card->app), name);
err = card->app->fnc.getattr (card->app, ctrl, name);
}
unlock_card (card);
return err;
}
/* Perform a SETATTR operation. */
gpg_error_t
app_setattr (card_t card, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
if (!card || !name || !*name || !value)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.setattr)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling setattr(%s)\n",
card->slot, xstrapptype (card->app), name);
err = card->app->fnc.setattr (card->app, ctrl, name, pincb, pincb_arg,
value, valuelen);
}
unlock_card (card);
return err;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_sign (card_t card, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.sign)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling sign(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
err = card->app->fnc.sign (card->app, ctrl, keyidstr, hashalgo,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation sign result: %s\n", gpg_strerror (err));
return err;
}
/* Create the signature using the INTERNAL AUTHENTICATE command and
return the allocated result in OUTDATA. If a PIN is required the
PINCB will be used to ask for the PIN; it should return the PIN in
an allocated buffer and put it into PIN. */
gpg_error_t
app_auth (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.auth)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling auth(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
err = card->app->fnc.auth (card->app, ctrl, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation auth result: %s\n", gpg_strerror (err));
return err;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
*r_info = 0;
if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.decipher)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling decipher(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
err = card->app->fnc.decipher (card->app, ctrl, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen,
r_info);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation decipher result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITECERT operation. */
gpg_error_t
app_writecert (card_t card, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *data, size_t datalen)
{
gpg_error_t err;
if (!card || !certidstr || !*certidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, certidstr)))
;
else if (!card->app->fnc.writecert)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling writecert(%s)\n",
card->slot, xstrapptype (card->app), certidstr);
err = card->app->fnc.writecert (card->app, ctrl, certidstr,
pincb, pincb_arg, data, datalen);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation writecert result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITEKEY operation. */
gpg_error_t
app_writekey (card_t card, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
if (!card || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.writekey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling writekey(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
err = card->app->fnc.writekey (card->app, ctrl, keyidstr, flags,
pincb, pincb_arg, keydata, keydatalen);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation writekey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GENKEY operation. */
gpg_error_t
app_genkey (card_t card, ctrl_t ctrl, const char *keynostr,
const char *keytype, unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!card || !keynostr || !*keynostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, keynostr)))
;
else if (!card->app->fnc.genkey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling genkey(%s)\n",
card->slot, xstrapptype (card->app), keynostr);
err = card->app->fnc.genkey (card->app, ctrl, keynostr, keytype, flags,
createtime, pincb, pincb_arg);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation genkey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GET CHALLENGE operation. This function is special as it
directly accesses the card without any application specific
wrapper. */
gpg_error_t
app_get_challenge (card_t card, ctrl_t ctrl,
size_t nbytes, unsigned char *buffer)
{
gpg_error_t err;
if (!card || !nbytes || !buffer)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if (!card->ref_count)
err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
else
err = iso7816_get_challenge (card->slot, nbytes, buffer);
unlock_card (card);
return err;
}
/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
gpg_error_t
app_change_pin (card_t card, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!card || !chvnostr || !*chvnostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.change_pin)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling change_pin(%s)\n",
card->slot, xstrapptype (card->app), chvnostr);
err = card->app->fnc.change_pin (card->app, ctrl,
chvnostr, flags, pincb, pincb_arg);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation change_pin result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a VERIFY operation without doing anything else. This may
be used to initialize a the PIN cache for long lasting other
operations. Its use is highly application dependent. */
gpg_error_t
app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!card || !keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.check_pin)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling check_pin(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
err = card->app->fnc.check_pin (card->app, ctrl, keyidstr,
pincb, pincb_arg);
}
unlock_card (card);
if (opt.verbose)
log_info ("operation check_pin result: %s\n", gpg_strerror (err));
return err;
}
static void
report_change (int slot, int old_status, int cur_status)
{
char *homestr, *envstr;
char *fname;
char templ[50];
FILE *fp;
snprintf (templ, sizeof templ, "reader_%d.status", slot);
fname = make_filename (gnupg_homedir (), templ, NULL );
fp = fopen (fname, "w");
if (fp)
{
fprintf (fp, "%s\n",
(cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
fclose (fp);
}
xfree (fname);
homestr = make_filename (gnupg_homedir (), NULL);
if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
log_error ("out of core while building environment\n");
else
{
gpg_error_t err;
const char *args[9], *envs[2];
char numbuf1[30], numbuf2[30], numbuf3[30];
envs[0] = envstr;
envs[1] = NULL;
sprintf (numbuf1, "%d", slot);
sprintf (numbuf2, "0x%04X", old_status);
sprintf (numbuf3, "0x%04X", cur_status);
args[0] = "--reader-port";
args[1] = numbuf1;
args[2] = "--old-code";
args[3] = numbuf2;
args[4] = "--new-code";
args[5] = numbuf3;
args[6] = "--status";
args[7] = ((cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
args[8] = NULL;
fname = make_filename (gnupg_homedir (), "scd-event", NULL);
err = gnupg_spawn_process_detached (fname, args, envs);
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("failed to run event handler '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
xfree (envstr);
}
xfree (homestr);
}
int
scd_update_reader_status_file (void)
{
card_t card, card_next;
int periodical_check_needed = 0;
npth_mutex_lock (&card_list_lock);
for (card = card_top; card; card = card_next)
{
int sw;
unsigned int status;
lock_card (card, NULL);
card_next = card->next;
if (card->reset_requested)
status = 0;
else
{
sw = apdu_get_status (card->slot, 0, &status);
if (sw == SW_HOST_NO_READER)
{
/* Most likely the _reader_ has been unplugged. */
status = 0;
}
else if (sw)
{
/* Get status failed. Ignore that. */
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
continue;
}
}
if (card->card_status != status)
{
report_change (card->slot, card->card_status, status);
send_client_notifications (card, status == 0);
if (status == 0)
{
log_debug ("Removal of a card: %d\n", card->slot);
pincache_put (NULL, card->slot, NULL, NULL, NULL, 0);
apdu_close_reader (card->slot);
deallocate_card (card);
}
else
{
card->card_status = status;
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
}
}
else
{
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
}
}
npth_mutex_unlock (&card_list_lock);
return periodical_check_needed;
}
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
gpg_error_t
initialize_module_command (void)
{
gpg_error_t err;
if (npth_mutex_init (&card_list_lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
return err;
}
return apdu_init ();
}
/* Sort helper for app_send_card_list. */
static int
compare_card_list_items (const void *arg_a, const void *arg_b)
{
const card_t a = *(const card_t *)arg_a;
const card_t b = *(const card_t *)arg_b;
return a->slot - b->slot;
}
/* Helper for send_card_and_app_list and app_switch_active_app. */
static gpg_error_t
send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl)
{
gpg_error_t err;
app_t a;
char buf[65];
char *p;
membuf_t mb;
int any = 0;
if (DIM (buf) < 2 * card->serialnolen + 1)
return 0; /* Oops. */
bin2hex (card->serialno, card->serialnolen, buf);
if (with_apps)
{
/* Note that in case the additional applications have not yet been
* added to the card context (which is commonly done by means of
* "SERIALNO --all", we do that here. */
err = select_all_additional_applications_internal (card);
if (err)
return err;
init_membuf (&mb, 256);
put_membuf_str (&mb, buf);
for (a = card->app; a; a = a->next)
{
if (!a->fnc.with_keygrip)
continue;
any = 1;
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, xstrapptype (a));
}
if (!any && card->app)
{
/* No card app supports the with_keygrip function. Use the
* main app as fallback. */
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, xstrapptype (card->app));
}
put_membuf (&mb, "", 1);
p = get_membuf (&mb, NULL);
if (!p)
return gpg_error_from_syserror ();
send_status_direct (ctrl, "SERIALNO", p);
xfree (p);
}
else
send_status_direct (ctrl, "SERIALNO", buf);
return 0;
}
/* Common code for app_send_card_list and app_send_active_apps. */
static gpg_error_t
send_card_and_app_list (ctrl_t ctrl, card_t wantcard, int with_apps)
{
gpg_error_t err;
card_t c;
card_t *cardlist = NULL;
int n, ncardlist;
npth_mutex_lock (&card_list_lock);
for (n=0, c = card_top; c; c = c->next)
n++;
cardlist = xtrycalloc (n, sizeof *cardlist);
if (!cardlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (ncardlist=0, c = card_top; c; c = c->next)
cardlist[ncardlist++] = c;
qsort (cardlist, ncardlist, sizeof *cardlist, compare_card_list_items);
for (n=0; n < ncardlist; n++)
{
if (wantcard && wantcard != cardlist[n])
continue;
err = send_serialno_and_app_status (cardlist[n], with_apps, ctrl);
if (err)
goto leave;
}
err = 0;
leave:
npth_mutex_unlock (&card_list_lock);
xfree (cardlist);
return err;
}
/* Send status lines with the serialno of all inserted cards. */
gpg_error_t
app_send_card_list (ctrl_t ctrl)
{
return send_card_and_app_list (ctrl, NULL, 0);
}
/* Send status lines with the serialno and appname of the current card
* or of all cards if CARD is NULL. */
gpg_error_t
app_send_active_apps (card_t card, ctrl_t ctrl)
{
return send_card_and_app_list (ctrl, card, 1);
}
/* Switch to APPNAME and print a respective status line with that app
* listed first. If APPNAME is NULL or the empty string no switching
* is done but the status line is printed anyway. */
gpg_error_t
app_switch_active_app (card_t card, ctrl_t ctrl, const char *appname)
{
gpg_error_t err;
apptype_t apptype;
if (!card)
return gpg_error (GPG_ERR_INV_VALUE);
err = lock_card (card, ctrl);
if (err)
return err;
/* Note that in case the additional applications have not yet been
* added to the card context (which is commonly done by means of
* "SERIALNO --all", we do that here. */
err = select_all_additional_applications_internal (card);
if (err)
goto leave;
if (appname && *appname)
{
apptype = apptype_from_name (appname);
if (!apptype)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
ctrl->current_apptype = apptype;
err = maybe_switch_app (ctrl, card, NULL);
if (err)
goto leave;
}
/* Print the status line. */
err = send_serialno_and_app_status (card, 1, ctrl);
leave:
unlock_card (card);
return err;
}
/* Execute an action for each app. ACTION can be one of:
*
* - KEYGRIP_ACTION_SEND_DATA
*
* If KEYGRIP_STR matches a public key of any active application
* send information as LF terminated data lines about the public
* key. The format of these lines is
* <keygrip> T <serialno> <idstr>
* If a match was found a pointer to the matching application is
* returned. With the KEYGRIP_STR given as NULL, lines for all
* keys (with CAPABILITY) will be send and the return value is
* GPG_ERR_TRUE.
*
* - KEYGRIP_ACTION_WRITE_STATUS
*
* Same as KEYGRIP_ACTION_SEND_DATA but uses status lines instead
* of data lines.
*
* - KEYGRIP_ACTION_LOOKUP
*
* Returns a pointer to the application matching KEYGRIP_STR but
* does not emit any status or data lines. If no key with that
* keygrip is available or KEYGRIP_STR is NULL, GPG_ERR_NOT_FOUND
* is returned.
*/
card_t
app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str,
int capability)
{
int locked = 0;
card_t c;
app_t a, a_prev;
npth_mutex_lock (&card_list_lock);
for (c = card_top; c; c = c->next)
{
if (lock_card (c, ctrl))
{
c = NULL;
goto leave_the_loop;
}
locked = 1;
a_prev = NULL;
for (a = c->app; a; a = a->next)
{
if (!a->fnc.with_keygrip)
continue;
/* Note that we need to do a re-select even for the current
* app because the last selected application (e.g. after
* init) might be a different one and we do not run
* maybe_switch_app here. Of course we we do this only iff
* we have an additional app. */
if (c->app->next)
{
if (run_reselect (ctrl, c, a, a_prev))
continue;
}
a_prev = a;
if (DBG_APP)
log_debug ("slot %d, app %s: calling with_keygrip(%s)\n",
c->slot, xstrapptype (a),
action == KEYGRIP_ACTION_SEND_DATA? "send_data":
action == KEYGRIP_ACTION_WRITE_STATUS? "write_data":
action == KEYGRIP_ACTION_LOOKUP? "lookup":"?");
if (!a->fnc.with_keygrip (a, ctrl, action, keygrip_str, capability))
goto leave_the_loop; /* ACTION_LOOKUP succeeded. */
}
/* Select the first app again. */
if (c->app->next)
run_reselect (ctrl, c, c->app, a_prev);
unlock_card (c);
locked = 0;
}
leave_the_loop:
/* Force switching of the app if the selected one is not the current
* one. Changing the current apptype is sufficient to do this. */
if (c && c->app && c->app->apptype != a->apptype)
ctrl->current_apptype = a->apptype;
if (locked && c)
{
unlock_card (c);
locked = 0;
}
npth_mutex_unlock (&card_list_lock);
return c;
}
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 9d9870636..60252c04f 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -1,3935 +1,3935 @@
/* ccid-driver.c - USB ChipCardInterfaceDevices driver
* Copyright (C) 2003, 2004, 2005, 2006, 2007
* 2008, 2009, 2013 Free Software Foundation, Inc.
* Written by Werner Koch.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, this file may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU General Public License. If you wish to
* allow use of your version of this file only under the terms of the
* GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR 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 AUTHOR 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.
*/
/* CCID (ChipCardInterfaceDevices) is a specification for accessing
smartcard via a reader connected to the USB.
This is a limited driver allowing to use some CCID drivers directly
without any other specila drivers. This is a fallback driver to be
used when nothing else works or the system should be kept minimal
for security reasons. It makes use of the libusb library to gain
portable access to USB.
This driver has been tested with the SCM SCR335 and SPR532
smartcard readers and requires that a reader implements APDU or
TPDU level exchange and does fully automatic initialization.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#if defined(HAVE_LIBUSB) || defined(TEST)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_NPTH
# include <npth.h>
#endif /*HAVE_NPTH*/
#include <libusb.h>
#include "scdaemon.h"
#include "iso7816.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
#define DRVNAME "ccid-driver: "
/* Max length of buffer with out CCID message header of 10-byte
Sending: 547 for RSA-4096 key import
APDU size = 540 (24+4+256+256)
- commnd + lc + le = 4 + 3 + 0
+ command + lc + le = 4 + 3 + 0
Sending: write data object of cardholder certificate
APDU size = 2048
- commnd + lc + le = 4 + 3 + 0
+ command + lc + le = 4 + 3 + 0
Receiving: 2048 for cardholder certificate
*/
#define CCID_MAX_BUF (2048+7+10)
/* CCID command timeout. */
#define CCID_CMD_TIMEOUT (5*1000)
/* Depending on how this source is used we either define our error
* output to go to stderr or to the GnuPG based logging functions. We
* use the latter when GNUPG_MAJOR_VERSION is defined. */
#if defined(GNUPG_MAJOR_VERSION)
# include "scdaemon.h"
# define DEBUGOUT(t) do { if (debug_level) \
log_debug (DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
log_debug (DRVNAME t,(a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c));} while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c),(d));} while (0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
log_printf (t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
log_printf (t,(a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
log_printf (t,(a),(b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
log_printf (t,(a),(b),(c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
log_printf ("\n"); } while (0)
#else /* Other usage of this source - don't use gnupg specifics. */
# define DEBUGOUT(t) do { if (debug_level) \
fprintf (stderr, DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c)); } while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c), (d));} while(0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
fprintf (stderr, t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
fprintf (stderr, t, (a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, t, (a), (b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, t, (a), (b), (c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
putc ('\n', stderr); } while (0)
#endif /* This source is not used by scdaemon. */
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
enum {
RDR_to_PC_NotifySlotChange= 0x50,
RDR_to_PC_HardwareError = 0x51,
PC_to_RDR_SetParameters = 0x61,
PC_to_RDR_IccPowerOn = 0x62,
PC_to_RDR_IccPowerOff = 0x63,
PC_to_RDR_GetSlotStatus = 0x65,
PC_to_RDR_Secure = 0x69,
PC_to_RDR_T0APDU = 0x6a,
PC_to_RDR_Escape = 0x6b,
PC_to_RDR_GetParameters = 0x6c,
PC_to_RDR_ResetParameters = 0x6d,
PC_to_RDR_IccClock = 0x6e,
PC_to_RDR_XfrBlock = 0x6f,
PC_to_RDR_Mechanical = 0x71,
PC_to_RDR_Abort = 0x72,
PC_to_RDR_SetDataRate = 0x73,
RDR_to_PC_DataBlock = 0x80,
RDR_to_PC_SlotStatus = 0x81,
RDR_to_PC_Parameters = 0x82,
RDR_to_PC_Escape = 0x83,
RDR_to_PC_DataRate = 0x84
};
/* Two macro to detect whether a CCID command has failed and to get
the error code. These macros assume that we can access the
mandatory first 10 bytes of a CCID message in BUF. */
#define CCID_COMMAND_FAILED(buf) ((buf)[7] & 0x40)
#define CCID_ERROR_CODE(buf) (((unsigned char *)(buf))[8])
/* Store information on the driver's state. A pointer to such a
structure is used as handle for most functions. */
struct ccid_driver_s
{
libusb_device_handle *idev;
unsigned int bai;
unsigned short id_vendor;
unsigned short id_product;
int ifc_no;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
int seqno;
unsigned char t1_ns;
unsigned char t1_nr;
unsigned char nonnull_nad;
int max_ifsd;
int max_ccid_msglen;
int ifsc;
unsigned char apdu_level:2; /* Reader supports short APDU level
exchange. With a value of 2 short
and extended level is supported.*/
unsigned int auto_voltage:1;
unsigned int auto_param:1;
unsigned int auto_pps:1;
unsigned int auto_ifsd:1;
unsigned int has_pinpad:2;
unsigned int enodev_seen:1;
int powered_off;
time_t last_progress; /* Last time we sent progress line. */
/* The progress callback and its first arg as supplied to
ccid_set_progress_cb. */
void (*progress_cb)(void *, const char *, int, int, int);
void *progress_cb_arg;
void (*prompt_cb)(void *, int);
void *prompt_cb_arg;
unsigned char intr_buf[64];
struct libusb_transfer *transfer;
};
static int initialized_usb; /* Tracks whether USB has been initialized. */
static int debug_level; /* Flag to control the debug output.
0 = No debugging
1 = USB I/O info
2 = Level 1 + T=1 protocol tracing
3 = Level 2 + USB/I/O tracing of SlotStatus.
*/
static int ccid_usb_thread_is_alive;
static unsigned int compute_edc (const unsigned char *data, size_t datalen,
int use_crc);
static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug);
static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug);
static int abort_cmd (ccid_driver_t handle, int seqno);
static int send_escape_cmd (ccid_driver_t handle, const unsigned char *data,
size_t datalen, unsigned char *result,
size_t resultmax, size_t *resultlen);
/* Convert a little endian stored 4 byte value into an unsigned
integer. */
static unsigned int
convert_le_u32 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
}
/* Convert a little endian stored 2 byte value into an unsigned
integer. */
static unsigned int
convert_le_u16 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8);
}
static void
set_msg_len (unsigned char *msg, unsigned int length)
{
msg[1] = length;
msg[2] = length >> 8;
msg[3] = length >> 16;
msg[4] = length >> 24;
}
static void
print_progress (ccid_driver_t handle)
{
time_t ct = time (NULL);
/* We don't want to print progress lines too often. */
if (ct == handle->last_progress)
return;
if (handle->progress_cb)
handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0);
handle->last_progress = ct;
}
/* Pint an error message for a failed CCID command including a textual
error code. MSG shall be the CCID message at a minimum of 10 bytes. */
static void
print_command_failed (const unsigned char *msg)
{
const char *t;
char buffer[100];
int ec;
if (!debug_level)
return;
ec = CCID_ERROR_CODE (msg);
switch (ec)
{
case 0x00: t = "Command not supported"; break;
case 0xE0: t = "Slot busy"; break;
case 0xEF: t = "PIN cancelled"; break;
case 0xF0: t = "PIN timeout"; break;
case 0xF2: t = "Automatic sequence ongoing"; break;
case 0xF3: t = "Deactivated Protocol"; break;
case 0xF4: t = "Procedure byte conflict"; break;
case 0xF5: t = "ICC class not supported"; break;
case 0xF6: t = "ICC protocol not supported"; break;
case 0xF7: t = "Bad checksum in ATR"; break;
case 0xF8: t = "Bad TS in ATR"; break;
case 0xFB: t = "An all inclusive hardware error occurred"; break;
case 0xFC: t = "Overrun error while talking to the ICC"; break;
case 0xFD: t = "Parity error while talking to the ICC"; break;
case 0xFE: t = "CCID timed out while talking to the ICC"; break;
case 0xFF: t = "Host aborted the current activity"; break;
default:
if (ec > 0 && ec < 128)
sprintf (buffer, "Parameter error at offset %d", ec);
else
sprintf (buffer, "Error code %02X", ec);
t = buffer;
break;
}
DEBUGOUT_1 ("CCID command failed: %s\n", t);
}
static void
print_pr_data (const unsigned char *data, size_t datalen, size_t off)
{
int any = 0;
for (; off < datalen; off++)
{
if (!any || !(off % 16))
{
if (any)
DEBUGOUT_LF ();
DEBUGOUT_1 (" [%04lu] ", (unsigned long) off);
}
DEBUGOUT_CONT_1 (" %02X", data[off]);
any = 1;
}
if (any && (off % 16))
DEBUGOUT_LF ();
}
static void
print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 7)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
}
static void
print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
msg[7] == 0? "auto":
msg[7] == 1? "5.0 V":
msg[7] == 2? "3.0 V":
msg[7] == 3? "1.8 V":"");
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_getparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_escape (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_iccclock (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]);
DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]);
DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_secure (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_mechanical (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_abort (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_unknown (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("Unknown PC_to_RDR command", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 0);
}
static void
print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 9)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]);
if (msg[8])
DEBUGOUT_1 (" bError ............: %u\n", msg[8]);
}
static void
print_r2p_datablock (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
if (msglen < 10)
return;
if (msg[9])
DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9],
msg[9] == 1? " (continued)":
msg[9] == 2? " (continues+ends)":
msg[9] == 3? " (continues+continued)":
msg[9] == 16? " (XferBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9],
msg[9] == 0? " (running)":
msg[9] == 1? " (stopped-L)":
msg[9] == 2? " (stopped-H)":
msg[9] == 3? " (stopped)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_parameters (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]);
if (msglen == 17 && msg[9] == 1)
{
/* Protocol T=1. */
DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]);
DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]);
DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]);
DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]);
DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]);
DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]);
DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_escape (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_datarate (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
if (msglen < 10)
return;
if (msglen >= 18)
{
DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
print_pr_data (msg, msglen, 18);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_unknown (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("Unknown RDR_to_PC command", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]);
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
/* Parse a CCID descriptor, optionally print all available features
and test whether this reader is usable by this driver. Returns 0
if it is usable.
Note, that this code is based on the one in lsusb.c of the
usb-utils package, I wrote on 2003-09-01. -wk. */
static int
parse_ccid_descriptor (ccid_driver_t handle, unsigned short bcd_device,
const unsigned char *buf, size_t buflen)
{
unsigned int i;
unsigned int us;
int have_t1 = 0, have_tpdu=0;
handle->nonnull_nad = 0;
handle->auto_ifsd = 0;
handle->max_ifsd = 32;
handle->has_pinpad = 0;
handle->apdu_level = 0;
handle->auto_voltage = 0;
handle->auto_param = 0;
handle->auto_pps = 0;
DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n",
handle->id_vendor, handle->id_product, bcd_device);
if (buflen < 54 || buf[0] < 54)
{
DEBUGOUT ("CCID device descriptor is too short\n");
return -1;
}
DEBUGOUT ("ChipCard Interface Descriptor:\n");
DEBUGOUT_1 (" bLength %5u\n", buf[0]);
DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]);
DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]);
if (buf[3] != 1 || buf[2] != 0)
DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)");
DEBUGOUT_LF ();
DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]);
DEBUGOUT_2 (" bVoltageSupport %5u %s\n",
buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
: buf[5] == 3? "1.8V":"?"));
us = convert_le_u32 (buf+6);
DEBUGOUT_1 (" dwProtocols %5u ", us);
if ((us & 1))
DEBUGOUT_CONT (" T=0");
if ((us & 2))
{
DEBUGOUT_CONT (" T=1");
have_t1 = 1;
}
if ((us & ~3))
DEBUGOUT_CONT (" (Invalid values detected)");
DEBUGOUT_LF ();
us = convert_le_u32(buf+10);
DEBUGOUT_1 (" dwDefaultClock %5u\n", us);
us = convert_le_u32(buf+14);
DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us);
DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]);
us = convert_le_u32(buf+19);
DEBUGOUT_1 (" dwDataRate %7u bps\n", us);
us = convert_le_u32(buf+23);
DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us);
DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]);
us = convert_le_u32(buf+28);
DEBUGOUT_1 (" dwMaxIFSD %5u\n", us);
handle->max_ifsd = us;
us = convert_le_u32(buf+32);
DEBUGOUT_1 (" dwSyncProtocols %08X ", us);
if ((us&1))
DEBUGOUT_CONT ( " 2-wire");
if ((us&2))
DEBUGOUT_CONT ( " 3-wire");
if ((us&4))
DEBUGOUT_CONT ( " I2C");
DEBUGOUT_LF ();
us = convert_le_u32(buf+36);
DEBUGOUT_1 (" dwMechanical %08X ", us);
if ((us & 1))
DEBUGOUT_CONT (" accept");
if ((us & 2))
DEBUGOUT_CONT (" eject");
if ((us & 4))
DEBUGOUT_CONT (" capture");
if ((us & 8))
DEBUGOUT_CONT (" lock");
DEBUGOUT_LF ();
us = convert_le_u32(buf+40);
DEBUGOUT_1 (" dwFeatures %08X\n", us);
if ((us & 0x0002))
{
DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n");
handle->auto_voltage = 1;
}
if ((us & 0x0004))
DEBUGOUT (" Auto activation on insert\n");
if ((us & 0x0008))
{
DEBUGOUT (" Auto voltage selection\n");
handle->auto_voltage = 1;
}
if ((us & 0x0010))
DEBUGOUT (" Auto clock change\n");
if ((us & 0x0020))
DEBUGOUT (" Auto baud rate change\n");
if ((us & 0x0040))
{
DEBUGOUT (" Auto parameter negotiation made by CCID\n");
handle->auto_param = 1;
}
else if ((us & 0x0080))
{
DEBUGOUT (" Auto PPS made by CCID\n");
handle->auto_pps = 1;
}
if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080))
DEBUGOUT (" WARNING: conflicting negotiation features\n");
if ((us & 0x0100))
DEBUGOUT (" CCID can set ICC in clock stop mode\n");
if ((us & 0x0200))
{
DEBUGOUT (" NAD value other than 0x00 accepted\n");
handle->nonnull_nad = 1;
}
if ((us & 0x0400))
{
DEBUGOUT (" Auto IFSD exchange\n");
handle->auto_ifsd = 1;
}
if ((us & 0x00010000))
{
DEBUGOUT (" TPDU level exchange\n");
have_tpdu = 1;
}
else if ((us & 0x00020000))
{
DEBUGOUT (" Short APDU level exchange\n");
handle->apdu_level = 1;
}
else if ((us & 0x00040000))
{
DEBUGOUT (" Short and extended APDU level exchange\n");
handle->apdu_level = 2;
}
else if ((us & 0x00070000))
DEBUGOUT (" WARNING: conflicting exchange levels\n");
us = convert_le_u32(buf+44);
DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us);
handle->max_ccid_msglen = us;
DEBUGOUT ( " bClassGetResponse ");
if (buf[48] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " bClassEnvelope ");
if (buf[49] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " wlcdLayout ");
if (!buf[50] && !buf[51])
DEBUGOUT_CONT ("none\n");
else
DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]);
DEBUGOUT_1 (" bPINSupport %5u ", buf[52]);
if ((buf[52] & 1))
{
DEBUGOUT_CONT ( " verification");
handle->has_pinpad |= 1;
}
if ((buf[52] & 2))
{
DEBUGOUT_CONT ( " modification");
handle->has_pinpad |= 2;
}
DEBUGOUT_LF ();
DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]);
if (buf[0] > 54)
{
DEBUGOUT (" junk ");
for (i=54; i < buf[0]-54; i++)
DEBUGOUT_CONT_1 (" %02X", buf[i]);
DEBUGOUT_LF ();
}
if (!have_t1 || !(have_tpdu || handle->apdu_level))
{
DEBUGOUT ("this drivers requires that the reader supports T=1, "
"TPDU or APDU level exchange - this is not available\n");
return -1;
}
/* SCM drivers get stuck in their internal USB stack if they try to
send a frame of n*wMaxPacketSize back to us. Given that
wMaxPacketSize is 64 for these readers we set the IFSD to a value
lower than that:
64 - 10 CCID header - 4 T1frame - 2 reserved = 48
Product Ids:
0xe001 - SCR 331
0x5111 - SCR 331-DI
0x5115 - SCR 335
0xe003 - SPR 532
The
0x5117 - SCR 3320 USB ID-000 reader
seems to be very slow but enabling this workaround boosts the
performance to a more or less acceptable level (tested by David).
*/
if (handle->id_vendor == VENDOR_SCM
&& handle->max_ifsd > 48
&& ( (handle->id_product == SCM_SCR331 && bcd_device < 0x0516)
||(handle->id_product == SCM_SCR331DI && bcd_device < 0x0620)
||(handle->id_product == SCM_SCR335 && bcd_device < 0x0514)
||(handle->id_product == SCM_SPR532 && bcd_device < 0x0504)
||(handle->id_product == SCM_SCR3320 && bcd_device < 0x0522)
))
{
DEBUGOUT ("enabling workaround for buggy SCM readers\n");
handle->max_ifsd = 48;
}
if (handle->id_vendor == VENDOR_GEMPC)
{
DEBUGOUT ("enabling product quirk: disable non-null NAD\n");
handle->nonnull_nad = 0;
}
return 0;
}
static char *
get_escaped_usb_string (libusb_device_handle *idev, int idx,
const char *prefix, const char *suffix)
{
int rc;
unsigned char buf[280];
unsigned char *s;
unsigned int langid;
size_t i, n, len;
char *result;
if (!idx)
return NULL;
/* Fixme: The next line is for the current Valgrid without support
for USB IOCTLs. */
memset (buf, 0, sizeof buf);
/* First get the list of supported languages and use the first one.
If we do don't find it we try to use English. Note that this is
all in a 2 bute Unicode encoding using little endian. */
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8), 0,
buf, sizeof buf, 1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc < 4)
langid = 0x0409; /* English. */
else
langid = (buf[3] << 8) | buf[2];
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8) + idx, langid,
buf, sizeof buf, 1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc < 2 || buf[1] != LIBUSB_DT_STRING)
return NULL; /* Error or not a string. */
len = buf[0];
if (len > rc)
return NULL; /* Larger than our buffer. */
for (s=buf+2, i=2, n=0; i+1 < len; i += 2, s += 2)
{
if (s[1])
n++; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
n += 3 ;
else
n++;
}
result = malloc (strlen (prefix) + n + strlen (suffix) + 1);
if (!result)
return NULL;
strcpy (result, prefix);
n = strlen (prefix);
for (s=buf+2, i=2; i+1 < len; i += 2, s += 2)
{
if (s[1])
result[n++] = '\xff'; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
{
sprintf (result+n, "%%%02X", *s);
n += 3;
}
else
result[n++] = *s;
}
strcpy (result+n, suffix);
return result;
}
/* This function creates an reader id to be used to find the same
physical reader after a reset. It returns an allocated and possibly
percent escaped string or NULL if not enough memory is available. */
static char *
make_reader_id (libusb_device_handle *idev,
unsigned int vendor, unsigned int product,
unsigned char serialno_index)
{
char *rid;
char prefix[20];
sprintf (prefix, "%04X:%04X:", (vendor & 0xffff), (product & 0xffff));
rid = get_escaped_usb_string (idev, serialno_index, prefix, ":0");
if (!rid)
{
rid = malloc (strlen (prefix) + 3 + 1);
if (!rid)
return NULL;
strcpy (rid, prefix);
strcat (rid, "X:0");
}
return rid;
}
/* Helper to find the endpoint from an interface descriptor. */
static int
find_endpoint (const struct libusb_interface_descriptor *ifcdesc, int mode)
{
int no;
int want_bulk_in = 0;
if (mode == 1)
want_bulk_in = 0x80;
for (no=0; no < ifcdesc->bNumEndpoints; no++)
{
const struct libusb_endpoint_descriptor *ep = ifcdesc->endpoint + no;
if (ep->bDescriptorType != LIBUSB_DT_ENDPOINT)
;
else if (mode == 2
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_INTERRUPT)
&& (ep->bEndpointAddress & 0x80))
return ep->bEndpointAddress;
else if ((mode == 0 || mode == 1)
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_BULK)
&& (ep->bEndpointAddress & 0x80) == want_bulk_in)
return ep->bEndpointAddress;
}
return -1;
}
/* Helper for scan_devices. This function returns true if a
requested device has been found or the caller should stop scanning
for other reasons. */
static void
scan_usb_device (int *count, char **rid_list, struct libusb_device *dev)
{
int ifc_no;
int set_no;
const struct libusb_interface_descriptor *ifcdesc;
char *rid;
libusb_device_handle *idev = NULL;
int err;
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
char *p;
err = libusb_get_device_descriptor (dev, &desc);
if (err)
return;
err = libusb_get_active_config_descriptor (dev, &config);
if (err)
return;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; set_no++)
{
ifcdesc = (config->interface[ifc_no].altsetting + set_no);
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
++*count;
err = libusb_open (dev, &idev);
if (err)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (err));
continue; /* with next setting. */
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
if (!rid)
{
libusb_free_config_descriptor (config);
return;
}
/* We are collecting infos about all available CCID
readers. Store them and continue. */
DEBUGOUT_2 ("found CCID reader %d (ID=%s)\n", *count, rid);
p = malloc ((*rid_list? strlen (*rid_list):0) + 1
+ strlen (rid) + 1);
if (p)
{
*p = 0;
if (*rid_list)
{
strcat (p, *rid_list);
free (*rid_list);
}
strcat (p, rid);
strcat (p, "\n");
*rid_list = p;
}
else /* Out of memory. */
{
libusb_free_config_descriptor (config);
free (rid);
return;
}
free (rid);
libusb_close (idev);
idev = NULL;
}
}
libusb_free_config_descriptor (config);
}
/* Scan all CCID devices.
The function returns 0 if a reader has been found or when a scan
returned without error.
R_RID should be the address where to store the list of reader_ids
we found. If on return this list is empty, no CCID device has been
found; otherwise it points to an allocated linked list of reader
IDs.
*/
static int
scan_devices (char **r_rid)
{
char *rid_list = NULL;
int count = 0;
libusb_device **dev_list = NULL;
libusb_device *dev;
int i;
ssize_t n;
/* Set return values to a default. */
if (r_rid)
*r_rid = NULL;
n = libusb_get_device_list (NULL, &dev_list);
for (i = 0; i < n; i++)
{
dev = dev_list[i];
scan_usb_device (&count, &rid_list, dev);
}
libusb_free_device_list (dev_list, 1);
*r_rid = rid_list;
return 0;
}
/* Set the level of debugging to LEVEL and return the old level. -1
just returns the old level. A level of 0 disables debugging, 1
enables debugging, 2 enables additional tracing of the T=1
protocol, 3 additionally enables debugging for GetSlotStatus, other
values are not yet defined.
Note that libusb may provide its own debugging feature which is
enabled by setting the envvar USB_DEBUG. */
int
ccid_set_debug_level (int level)
{
int old = debug_level;
if (level != -1)
debug_level = level;
return old;
}
char *
ccid_get_reader_list (void)
{
char *reader_list;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return NULL;
}
initialized_usb = 1;
}
if (scan_devices (&reader_list))
return NULL; /* Error. */
return reader_list;
}
/* Vendor specific custom initialization. */
static int
ccid_vendor_specific_init (ccid_driver_t handle)
{
if (handle->id_vendor == VENDOR_VEGA && handle->id_product == VEGA_ALPHA)
{
int r;
/*
* Vega alpha has a feature to show retry counter on the pinpad
* display. But it assumes that the card returns the value of
* retry counter by VERIFY with empty data (return code of
* 63Cx). Unfortunately, existing OpenPGP cards don't support
* VERIFY command with empty data. This vendor specific command
* sequence is to disable the feature.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
r = send_escape_cmd (handle, cmd, sizeof (cmd), NULL, 0, NULL);
if (r != 0 && r != CCID_DRIVER_ERR_CARD_INACTIVE
&& r != CCID_DRIVER_ERR_NO_CARD)
return r;
}
return 0;
}
#define MAX_DEVICE 4 /* See MAX_READER in apdu.c. */
struct ccid_dev_table {
int n; /* Index to ccid_usb_dev_list */
int interface_number;
int setting_number;
unsigned char *ifcdesc_extra;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
size_t ifcdesc_extra_len;
};
static libusb_device **ccid_usb_dev_list;
static struct ccid_dev_table ccid_dev_table[MAX_DEVICE];
gpg_error_t
ccid_dev_scan (int *idx_max_p, void **t_p)
{
ssize_t n;
libusb_device *dev;
int i;
int ifc_no;
int set_no;
int idx = 0;
int err = 0;
*idx_max_p = 0;
*t_p = NULL;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return gpg_error (GPG_ERR_ENODEV);
}
initialized_usb = 1;
}
n = libusb_get_device_list (NULL, &ccid_usb_dev_list);
for (i = 0; i < n; i++)
{
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
dev = ccid_usb_dev_list[i];
if (libusb_get_device_descriptor (dev, &desc))
continue;
if (libusb_get_active_config_descriptor (dev, &config))
continue;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting;
set_no++)
{
const struct libusb_interface_descriptor *ifcdesc;
ifcdesc = &config->interface[ifc_no].altsetting[set_no];
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
/* Found a reader. */
unsigned char *ifcdesc_extra;
ifcdesc_extra = malloc (ifcdesc->extra_length);
if (!ifcdesc_extra)
{
err = gpg_error_from_syserror ();
libusb_free_config_descriptor (config);
goto scan_finish;
}
memcpy (ifcdesc_extra, ifcdesc->extra, ifcdesc->extra_length);
ccid_dev_table[idx].n = i;
ccid_dev_table[idx].interface_number = ifc_no;
ccid_dev_table[idx].setting_number = set_no;
ccid_dev_table[idx].ifcdesc_extra = ifcdesc_extra;
ccid_dev_table[idx].ifcdesc_extra_len = ifcdesc->extra_length;
ccid_dev_table[idx].ep_bulk_out = find_endpoint (ifcdesc, 0);
ccid_dev_table[idx].ep_bulk_in = find_endpoint (ifcdesc, 1);
ccid_dev_table[idx].ep_intr = find_endpoint (ifcdesc, 2);
idx++;
if (idx >= MAX_DEVICE)
{
libusb_free_config_descriptor (config);
err = 0;
goto scan_finish;
}
}
}
libusb_free_config_descriptor (config);
}
scan_finish:
if (err)
{
for (i = 0; i < idx; i++)
{
free (ccid_dev_table[idx].ifcdesc_extra);
ccid_dev_table[idx].n = 0;
ccid_dev_table[idx].interface_number = 0;
ccid_dev_table[idx].setting_number = 0;
ccid_dev_table[idx].ifcdesc_extra = NULL;
ccid_dev_table[idx].ifcdesc_extra_len = 0;
ccid_dev_table[idx].ep_bulk_out = 0;
ccid_dev_table[idx].ep_bulk_in = 0;
ccid_dev_table[idx].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
else
{
*idx_max_p = idx;
if (idx)
*t_p = ccid_dev_table;
else
*t_p = NULL;
}
return err;
}
void
ccid_dev_scan_finish (void *tbl0, int max)
{
int i;
struct ccid_dev_table *tbl = tbl0;
for (i = 0; i < max; i++)
{
free (tbl[i].ifcdesc_extra);
tbl[i].n = 0;
tbl[i].interface_number = 0;
tbl[i].setting_number = 0;
tbl[i].ifcdesc_extra = NULL;
tbl[i].ifcdesc_extra_len = 0;
tbl[i].ep_bulk_out = 0;
tbl[i].ep_bulk_in = 0;
tbl[i].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
unsigned int
ccid_get_BAI (int idx, void *tbl0)
{
int n;
int bus, addr, intf;
unsigned int bai;
libusb_device *dev;
struct ccid_dev_table *tbl = tbl0;
n = tbl[idx].n;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
intf = tbl[idx].interface_number;
bai = (bus << 16) | (addr << 8) | intf;
return bai;
}
int
ccid_compare_BAI (ccid_driver_t handle, unsigned int bai)
{
return handle->bai == bai;
}
static void
intr_cb (struct libusb_transfer *transfer)
{
ccid_driver_t handle = transfer->user_data;
DEBUGOUT_1 ("CCID: interrupt callback %d\n", transfer->status);
if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
{
int err;
submit_again:
/* Submit the URB again to keep watching the INTERRUPT transfer. */
err = libusb_submit_transfer (transfer);
if (err == LIBUSB_ERROR_NO_DEVICE)
goto device_removed;
DEBUGOUT_1 ("CCID submit transfer again %d\n", err);
}
else if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
{
if (transfer->actual_length == 2
&& transfer->buffer[0] == 0x50
&& (transfer->buffer[1] & 1) == 0)
{
DEBUGOUT ("CCID: card removed\n");
handle->powered_off = 1;
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
else
{
/* Event other than card removal. */
goto submit_again;
}
}
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
handle->powered_off = 1;
else
{
device_removed:
DEBUGOUT ("CCID: device removed\n");
handle->powered_off = 1;
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
}
static void
ccid_setup_intr (ccid_driver_t handle)
{
struct libusb_transfer *transfer;
int err;
transfer = libusb_alloc_transfer (0);
handle->transfer = transfer;
libusb_fill_interrupt_transfer (transfer, handle->idev, handle->ep_intr,
handle->intr_buf, sizeof (handle->intr_buf),
intr_cb, handle, 0);
err = libusb_submit_transfer (transfer);
DEBUGOUT_2 ("CCID submit transfer (%x): %d", handle->ep_intr, err);
}
static void *
ccid_usb_thread (void *arg)
{
libusb_context *ctx = arg;
while (ccid_usb_thread_is_alive)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_handle_events_completed (ctx, NULL);
#ifdef USE_NPTH
npth_protect ();
#endif
}
return NULL;
}
static int
ccid_open_usb_reader (const char *spec_reader_name,
int idx, void *ccid_table0,
ccid_driver_t *handle, char **rdrname_p)
{
libusb_device *dev;
libusb_device_handle *idev = NULL;
char *rid = NULL;
int rc = 0;
int ifc_no, set_no;
struct libusb_device_descriptor desc;
int n;
int bus, addr;
unsigned int bai;
struct ccid_dev_table *ccid_table = ccid_table0;
n = ccid_table[idx].n;
ifc_no = ccid_table[idx].interface_number;
set_no = ccid_table[idx].setting_number;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
bai = (bus << 16) | (addr << 8) | ifc_no;
rc = libusb_open (dev, &idev);
if (rc)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (rc));
free (*handle);
*handle = NULL;
return rc;
}
if (ccid_usb_thread_is_alive++ == 0)
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
{
DEBUGOUT_1 ("npth_attr_init failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, ccid_usb_thread, NULL);
if (err)
{
DEBUGOUT_1 ("npth_create failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_attr_destroy (&tattr);
}
rc = libusb_get_device_descriptor (dev, &desc);
if (rc)
{
DEBUGOUT ("get_device_descripor failed\n");
goto leave;
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
/* Check to see if reader name matches the spec. */
if (spec_reader_name
&& strncmp (rid, spec_reader_name, strlen (spec_reader_name)))
{
DEBUGOUT ("device not matched\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
(*handle)->id_vendor = desc.idVendor;
(*handle)->id_product = desc.idProduct;
(*handle)->idev = idev;
(*handle)->bai = bai;
(*handle)->ifc_no = ifc_no;
(*handle)->ep_bulk_out = ccid_table[idx].ep_bulk_out;
(*handle)->ep_bulk_in = ccid_table[idx].ep_bulk_in;
(*handle)->ep_intr = ccid_table[idx].ep_intr;
DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", idx, rid);
if (parse_ccid_descriptor (*handle, desc.bcdDevice,
ccid_table[idx].ifcdesc_extra,
ccid_table[idx].ifcdesc_extra_len))
{
DEBUGOUT ("device not supported\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
rc = libusb_claim_interface (idev, ifc_no);
if (rc)
{
DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
rc = CCID_DRIVER_ERR_CARD_IO_ERROR;
goto leave;
}
/* Submit SET_INTERFACE control transfer which can reset the device. */
rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no);
if (rc)
{
DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc);
rc = CCID_DRIVER_ERR_CARD_IO_ERROR;
goto leave;
}
rc = ccid_vendor_specific_init (*handle);
leave:
if (rc)
{
--ccid_usb_thread_is_alive;
free (rid);
libusb_close (idev);
free (*handle);
*handle = NULL;
}
else
{
if (rdrname_p)
*rdrname_p = rid;
else
free (rid);
}
return rc;
}
/* Open the reader with the internal number READERNO and return a
pointer to be used as handle in HANDLE. Returns 0 on success. */
int
ccid_open_reader (const char *spec_reader_name, int idx,
void *ccid_table0,
ccid_driver_t *handle, char **rdrname_p)
{
struct ccid_dev_table *ccid_table = ccid_table0;
*handle = calloc (1, sizeof **handle);
if (!*handle)
{
DEBUGOUT ("out of memory\n");
return CCID_DRIVER_ERR_OUT_OF_CORE;
}
return ccid_open_usb_reader (spec_reader_name, idx, ccid_table,
handle, rdrname_p);
}
int
ccid_require_get_status (ccid_driver_t handle)
{
/* When a card reader supports interrupt transfer to check the
status of card, it is possible to submit only an interrupt
transfer, and no check is required by application layer. USB can
detect removal of a card and can detect removal of a reader.
*/
if (handle->ep_intr >= 0)
return 0;
/* Libusb actually detects the removal of USB device in use.
However, there is no good API to handle the removal (yet),
cleanly and with good portability.
There is libusb_set_pollfd_notifiers function, but it doesn't
offer libusb_device_handle* data to its callback. So, when it
watches multiple devices, there is no way to know which device is
removed.
Once, we will have a good programming interface of libusb, we can
list tokens (with no interrupt transfer support, but always with
card inserted) here to return 0, so that scdaemon can submit
minimum packet on wire.
*/
return 1;
}
static int
send_power_off (ccid_driver_t handle)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
msg[0] = PC_to_RDR_IccPowerOff;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (!rc)
bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
seqno, 2000, 0);
return rc;
}
static void
do_close_reader (ccid_driver_t handle)
{
int rc;
if (!handle->powered_off)
send_power_off (handle);
if (handle->transfer)
{
if (!handle->powered_off)
{
DEBUGOUT ("libusb_cancel_transfer\n");
rc = libusb_cancel_transfer (handle->transfer);
if (rc != LIBUSB_ERROR_NOT_FOUND)
while (!handle->powered_off)
{
DEBUGOUT ("libusb_handle_events_completed\n");
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_handle_events_completed (NULL, &handle->powered_off);
#ifdef USE_NPTH
npth_protect ();
#endif
}
}
libusb_free_transfer (handle->transfer);
handle->transfer = NULL;
}
libusb_release_interface (handle->idev, handle->ifc_no);
--ccid_usb_thread_is_alive;
libusb_close (handle->idev);
handle->idev = NULL;
}
int
ccid_set_progress_cb (ccid_driver_t handle,
void (*cb)(void *, const char *, int, int, int),
void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->progress_cb = cb;
handle->progress_cb_arg = cb_arg;
return 0;
}
int
ccid_set_prompt_cb (ccid_driver_t handle,
void (*cb)(void *, int), void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->prompt_cb = cb;
handle->prompt_cb_arg = cb_arg;
return 0;
}
/* Close the reader HANDLE. */
int
ccid_close_reader (ccid_driver_t handle)
{
if (!handle)
return 0;
do_close_reader (handle);
free (handle);
return 0;
}
/* Return False if a card is present and powered. */
int
ccid_check_card_presence (ccid_driver_t handle)
{
(void)handle; /* Not yet implemented. */
return -1;
}
/* Write a MSG of length MSGLEN to the designated bulk out endpoint.
Returns 0 on success. */
static int
bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug)
{
int rc;
int transferred;
/* No need to continue and clutter the log with USB write error
messages after we got the first ENODEV. */
if (handle->enodev_seen)
return CCID_DRIVER_ERR_NO_READER;
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (msglen? msg[0]:0)
{
case PC_to_RDR_IccPowerOn:
print_p2r_iccpoweron (msg, msglen);
break;
case PC_to_RDR_IccPowerOff:
print_p2r_iccpoweroff (msg, msglen);
break;
case PC_to_RDR_GetSlotStatus:
print_p2r_getslotstatus (msg, msglen);
break;
case PC_to_RDR_XfrBlock:
print_p2r_xfrblock (msg, msglen);
break;
case PC_to_RDR_GetParameters:
print_p2r_getparameters (msg, msglen);
break;
case PC_to_RDR_ResetParameters:
print_p2r_resetparameters (msg, msglen);
break;
case PC_to_RDR_SetParameters:
print_p2r_setparameters (msg, msglen);
break;
case PC_to_RDR_Escape:
print_p2r_escape (msg, msglen);
break;
case PC_to_RDR_IccClock:
print_p2r_iccclock (msg, msglen);
break;
case PC_to_RDR_T0APDU:
print_p2r_to0apdu (msg, msglen);
break;
case PC_to_RDR_Secure:
print_p2r_secure (msg, msglen);
break;
case PC_to_RDR_Mechanical:
print_p2r_mechanical (msg, msglen);
break;
case PC_to_RDR_Abort:
print_p2r_abort (msg, msglen);
break;
case PC_to_RDR_SetDataRate:
print_p2r_setdatarate (msg, msglen);
break;
default:
print_p2r_unknown (msg, msglen);
break;
}
}
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
msg, msglen, &transferred,
5000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc == 0 && transferred == msglen)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_bulk_write error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
{
handle->enodev_seen = 1;
return CCID_DRIVER_ERR_NO_READER;
}
}
return 0;
}
/* Read a maximum of LENGTH bytes from the bulk in endpoint into
BUFFER and return the actual read number if bytes in NREAD. SEQNO
is the sequence number used to send the request and EXPECTED_TYPE
the type of message we expect. Does checks on the ccid
header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to
avoid debug messages in case of no error; this can be overridden
with a glibal debug level of at least 3. Returns 0 on success. */
static int
bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug)
{
int rc;
int msglen;
int notified = 0;
int bwi = 1;
/* Fixme: The next line for the current Valgrind without support
for USB IOCTLs. */
memset (buffer, 0, length);
retry:
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
buffer, length, &msglen, bwi*timeout);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
{
handle->enodev_seen = 1;
return CCID_DRIVER_ERR_NO_READER;
}
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 0)
return CCID_DRIVER_ERR_INV_VALUE; /* Faulty libusb. */
*nread = msglen;
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
abort_cmd (handle, seqno);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[6] != seqno)
{
DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
seqno, buffer[6]);
/* Retry until we are synced again. */
goto retry;
}
/* We need to handle the time extension request before we check that
we got the expected message type. This is in particular required
for the Cherry keyboard which sends a time extension request for
each key hit. */
if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80)
{
/* Card present and active, time extension requested. */
DEBUGOUT_2 ("time extension requested (%02X,%02X)\n",
buffer[7], buffer[8]);
bwi = 1;
if (buffer[8] != 0 && buffer[8] != 0xff)
bwi = buffer[8];
/* Gnuk enhancement to prompt user input by ack button */
if (buffer[8] == 0xff && !notified)
{
notified = 1;
handle->prompt_cb (handle->prompt_cb_arg, 1);
}
goto retry;
}
if (notified)
handle->prompt_cb (handle->prompt_cb_arg, 0);
if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus)
{
DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
abort_cmd (handle, seqno);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (buffer[0])
{
case RDR_to_PC_DataBlock:
print_r2p_datablock (buffer, msglen);
break;
case RDR_to_PC_SlotStatus:
print_r2p_slotstatus (buffer, msglen);
break;
case RDR_to_PC_Parameters:
print_r2p_parameters (buffer, msglen);
break;
case RDR_to_PC_Escape:
print_r2p_escape (buffer, msglen);
break;
case RDR_to_PC_DataRate:
print_r2p_datarate (buffer, msglen);
break;
default:
print_r2p_unknown (buffer, msglen);
break;
}
}
if (CCID_COMMAND_FAILED (buffer))
print_command_failed (buffer);
/* Check whether a card is at all available. Note: If you add new
error codes here, check whether they need to be ignored in
send_escape_cmd. */
switch ((buffer[7] & 0x03))
{
case 0: /* no error */ break;
case 1: rc = CCID_DRIVER_ERR_CARD_INACTIVE; break;
case 2: rc = CCID_DRIVER_ERR_NO_CARD; break;
case 3: /* RFU */ break;
}
if (rc)
{
/*
* Communication failure by device side.
* Possibly, it was forcibly suspended and resumed.
*
* Only detect this kind of failure when interrupt transfer is
* not supported. For card reader with interrupt transfer
* support removal is detected by intr_cb.
*/
if (handle->ep_intr < 0)
{
DEBUGOUT ("CCID: card inactive/removed\n");
handle->powered_off = 1;
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
}
return rc;
}
/* Send an abort sequence and wait until everything settled. */
static int
abort_cmd (ccid_driver_t handle, int seqno)
{
int rc;
unsigned char dummybuf[8];
unsigned char msg[100];
int msglen;
seqno &= 0xff;
DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno);
/* Send the abort command to the control pipe. Note that we don't
need to keep track of sent abort commands because there should
never be another thread using the same slot concurrently. */
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_control_transfer (handle->idev,
0x21,/* bmRequestType: host-to-device,
class specific, to interface. */
1, /* ABORT */
(seqno << 8 | 0 /* slot */),
handle->ifc_no,
dummybuf, 0,
1000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_control_msg error: %s\n", libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
/* Now send the abort command to the bulk out pipe using the same
SEQNO and SLOT. Do this in a loop to so that all seqno are
tried. */
seqno--; /* Adjust for next increment. */
do
{
int transferred;
seqno++;
msg[0] = PC_to_RDR_Abort;
msg[5] = 0; /* slot */
msg[6] = seqno;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msglen = 10;
set_msg_len (msg, 0);
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
msg, msglen, &transferred,
5000 /* ms timeout */);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc == 0 && transferred == msglen)
rc = 0;
else if (rc)
DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n",
libusb_error_name (rc));
if (rc)
return rc;
#ifdef USE_NPTH
npth_unprotect ();
#endif
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
msg, sizeof msg, &msglen,
5000 /*ms timeout*/);
#ifdef USE_NPTH
npth_protect ();
#endif
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n",
libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n",
(unsigned int)msglen);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n",
msg[7], msg[8], msg[9]);
if (CCID_COMMAND_FAILED (msg))
print_command_failed (msg);
}
while (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno);
handle->seqno = ((seqno + 1) & 0xff);
DEBUGOUT ("sending abort sequence succeeded\n");
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE. IF RESULT is not NULL, the result from the
operation will get returned in RESULT and its length in RESULTLEN.
If the response is larger than RESULTMAX, an error is returned and
the required buffer length returned in RESULTLEN. */
static int
send_escape_cmd (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *result, size_t resultmax, size_t *resultlen)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
if (resultlen)
*resultlen = 0;
if (datalen > sizeof msg - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
memcpy (msg+10, data, datalen);
msglen = 10 + datalen;
set_msg_len (msg, datalen);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape,
seqno, 5000, 0);
if (result)
switch (rc)
{
/* We need to ignore certain errorcode here. */
case 0:
case CCID_DRIVER_ERR_CARD_INACTIVE:
case CCID_DRIVER_ERR_NO_CARD:
{
if (msglen > resultmax)
rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */
else
{
memcpy (result, msg, msglen);
if (resultlen)
*resultlen = msglen;
rc = 0;
}
}
break;
default:
break;
}
return rc;
}
int
ccid_transceive_escape (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp);
}
/* experimental */
int
ccid_poll (ccid_driver_t handle)
{
int rc;
unsigned char msg[10];
int msglen;
int i, j;
rc = libusb_interrupt_transfer (handle->idev, handle->ep_intr,
msg, sizeof msg, &msglen,
0 /* ms timeout */ );
if (rc == LIBUSB_ERROR_TIMEOUT)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_intr_read error: %s\n", libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 1)
{
DEBUGOUT ("intr-in msg too short\n");
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[0] == RDR_to_PC_NotifySlotChange)
{
DEBUGOUT ("notify slot change:");
for (i=1; i < msglen; i++)
for (j=0; j < 4; j++)
DEBUGOUT_CONT_3 (" %d:%c%c",
(i-1)*4+j,
(msg[i] & (1<<(j*2)))? 'p':'-',
(msg[i] & (2<<(j*2)))? '*':' ');
DEBUGOUT_LF ();
}
else if (msg[0] == RDR_to_PC_HardwareError)
{
DEBUGOUT ("hardware error occurred\n");
}
else
{
DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
}
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE */
int
ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
int retries = 0;
if (handle->powered_off)
return CCID_DRIVER_ERR_NO_READER;
/* If the card (with its lower-level driver) doesn't require
GET_STATUS on wire (because it supports INTERRUPT transfer for
status change, or it's a token which has a card always inserted),
no need to send on wire. */
if (!on_wire && !ccid_require_get_status (handle))
{
/* Setup interrupt transfer at the initial call of slot_status
with ON_WIRE == 0 */
if (handle->transfer == NULL && handle->ep_intr >= 0)
ccid_setup_intr (handle);
*statusbits = 0;
return 0;
}
retry:
msg[0] = PC_to_RDR_GetSlotStatus;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
rc = bulk_out (handle, msg, 10, 1);
if (rc)
return rc;
/* Note that we set the NO_DEBUG flag here, so that the logs won't
get cluttered up by a ticker function checking for the slot
status and debugging enabled. */
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
seqno, retries? 1000 : 200, 1);
if (rc == CCID_DRIVER_ERR_CARD_IO_ERROR && retries < 3)
{
if (!retries)
{
DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n");
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
libusb_clear_halt (handle->idev, handle->ep_bulk_out);
#ifdef USE_NPTH
npth_protect ();
#endif
}
else
DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n");
retries++;
goto retry;
}
if (rc && rc != CCID_DRIVER_ERR_NO_CARD && rc != CCID_DRIVER_ERR_CARD_INACTIVE)
return rc;
*statusbits = (msg[7] & 3);
return 0;
}
/* Parse ATR string (of ATRLEN) and update parameters at PARAM.
Calling this routine, it should prepare default values at PARAM
beforehand. This routine assumes that card is accessed by T=1
protocol. It doesn't analyze historical bytes at all.
Returns < 0 value on error:
-1 for parse error or integrity check error
-2 for card doesn't support T=1 protocol
-3 for parameters are nod explicitly defined by ATR
-4 for this driver doesn't support CRC
Returns >= 0 on success:
0 for card is negotiable mode
1 for card is specific mode (and not negotiable)
*/
static int
update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen)
{
int i = -1;
int t, y, chk;
int historical_bytes_num, negotiable = 1;
#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0)
NEXTBYTE ();
if (atr[i] == 0x3F)
param[1] |= 0x02; /* Convention is inverse. */
NEXTBYTE ();
y = (atr[i] >> 4);
historical_bytes_num = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{
param[0] = atr[i]; /* TA1 - Fi & Di */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB1 - ignore */
if ((y & 4))
{
param[2] = atr[i]; /* TC1 - Guard Time */
NEXTBYTE ();
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TD1 */
t = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{ /* TA2 - PPS mode */
if ((atr[i] & 0x0f) != 1)
return -2; /* Wrong card protocol (!= 1). */
if ((atr[i] & 0x10) != 0x10)
return -3; /* Transmission parameters are implicitly defined. */
negotiable = 0; /* TA2 means specific mode. */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB2 - ignore */
if ((y & 4))
NEXTBYTE (); /* TC2 - ignore */
if ((y & 8))
{
y = (atr[i] >> 4); /* TD2 */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
while (y)
{
if ((y & 1))
{ /* TAx */
if (t == 1)
param[5] = atr[i]; /* IFSC */
else if (t == 15)
/* XXX: check voltage? */
param[4] = (atr[i] >> 6); /* ClockStop */
NEXTBYTE ();
}
if ((y & 2))
{
if (t == 1)
param[3] = atr[i]; /* TBx - BWI & CWI */
NEXTBYTE ();
}
if ((y & 4))
{
if (t == 1)
param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */
NEXTBYTE ();
if (param[1] & 0x01)
return -4; /* CRC not supported yet. */
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TDx */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
}
}
i += historical_bytes_num - 1;
NEXTBYTE ();
if (atrlen != i+1)
return -1;
#undef NEXTBYTE
chk = 0;
do
{
chk ^= atr[i];
i--;
}
while (i > 0);
if (chk != 0)
return -1;
return negotiable;
}
/* Return the ATR of the card. This is not a cached value and thus an
actual reset is done. */
int
ccid_get_atr (ccid_driver_t handle,
unsigned char *atr, size_t maxatrlen, size_t *atrlen)
{
int rc;
int statusbits;
unsigned char msg[100];
unsigned char *tpdu;
size_t msglen, tpdulen;
unsigned char seqno;
int use_crc = 0;
unsigned int edc;
int tried_iso = 0;
int got_param;
unsigned char param[7] = { /* For Protocol T=1 */
0x11, /* bmFindexDindex */
0x10, /* bmTCCKST1 */
0x00, /* bGuardTimeT1 */
0x4d, /* bmWaitingIntegersT1 */
0x00, /* bClockStop */
0x20, /* bIFSC */
0x00 /* bNadValue */
};
/* First check whether a card is available. */
rc = ccid_slot_status (handle, &statusbits, 1);
if (rc)
return rc;
if (statusbits == 2)
return CCID_DRIVER_ERR_NO_CARD;
/*
* In the first invocation of ccid_slot_status, card reader may
* return CCID_DRIVER_ERR_CARD_INACTIVE and handle->powered_off may
* become 1. Because inactive card is no problem (we are turning it
* ON here), clear the flag.
*/
handle->powered_off = 0;
/* For an inactive and also for an active card, issue the PowerOn
command to get the ATR. */
again:
msg[0] = PC_to_RDR_IccPowerOn;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
/* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
msg[7] = handle->auto_voltage ? 0 : 1;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb
&& ((handle->id_vendor == VENDOR_CHERRY
&& handle->id_product == 0x0005)
|| (handle->id_vendor == VENDOR_GEMPC
&& handle->id_product == 0x4433)
))
{
tried_iso = 1;
/* Try switching to ISO mode. */
if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2,
NULL, 0, NULL))
goto again;
}
else if (statusbits == 0 && CCID_COMMAND_FAILED (msg))
{
/* Card was active already, and something went wrong with
PC_to_RDR_IccPowerOn command. It may be baud-rate mismatch
between the card and the reader. To recover from this state,
send PC_to_RDR_IccPowerOff command to reset the card and try
again.
*/
rc = send_power_off (handle);
if (rc)
return rc;
statusbits = 1;
goto again;
}
else if (CCID_COMMAND_FAILED (msg))
return CCID_DRIVER_ERR_CARD_IO_ERROR;
handle->powered_off = 0;
if (atr)
{
size_t n = msglen - 10;
if (n > maxatrlen)
n = maxatrlen;
memcpy (atr, msg+10, n);
*atrlen = n;
}
param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0;
rc = update_param_by_atr (param, msg+10, msglen - 10);
if (rc < 0)
{
DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
got_param = 0;
if (handle->auto_param)
{
msg[0] = PC_to_RDR_GetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (!rc)
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 2000, 0);
if (rc)
DEBUGOUT ("GetParameters failed\n");
else if (msglen == 17 && msg[9] == 1)
got_param = 1;
}
else if (handle->auto_pps)
;
else if (rc == 1) /* It's negotiable, send PPS. */
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0;
msg[9] = 0;
msg[10] = 0xff; /* PPSS */
msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */
msg[12] = param[0]; /* PPS1: Fi / Di */
msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */
set_msg_len (msg, 4);
msglen = 10 + 4;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (msglen != 10 + 4)
{
DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0])
{
DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
/* Setup parameters to select T=1. */
msg[0] = PC_to_RDR_SetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 1; /* Select T=1. */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
if (!got_param)
memcpy (&msg[10], param, 7);
set_msg_len (msg, 7);
msglen = 10 + 7;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 5000, 0);
if (rc)
DEBUGOUT ("SetParameters failed (ignored)\n");
if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 )
handle->ifsc = msg[15];
else
handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */
if (handle->nonnull_nad && msglen > 16 && msg[16] == 0)
{
DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n");
handle->nonnull_nad = 0;
}
handle->t1_ns = 0;
handle->t1_nr = 0;
/* Send an S-Block with our maximum IFSD to the CCID. */
if (!handle->apdu_level && !handle->auto_ifsd)
{
tpdu = msg+10;
/* NAD: DAD=1, SAD=0 */
tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0;
tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */
tpdu[2] = 1;
tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
msglen = 10 + tpdulen;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, 5000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (tpdulen < 4)
return CCID_DRIVER_ERR_ABORTED;
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1)
{
DEBUGOUT ("invalid response for S-block (Change-IFSD)\n");
return -1;
}
DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]);
}
return 0;
}
static unsigned int
compute_edc (const unsigned char *data, size_t datalen, int use_crc)
{
if (use_crc)
{
return 0x42; /* Not yet implemented. */
}
else
{
unsigned char crc = 0;
for (; datalen; datalen--)
crc ^= *data++;
return crc;
}
}
/* Return true if APDU is an extended length one. */
static int
is_exlen_apdu (const unsigned char *apdu, size_t apdulen)
{
if (apdulen < 7 || apdu[4])
return 0; /* Too short or no Z byte. */
return 1;
}
/* Helper for ccid_transceive used for APDU level exchanges. */
static int
ccid_transceive_apdu_level (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_len,
unsigned char *resp, size_t maxresplen,
size_t *nresp)
{
int rc;
unsigned char msg[CCID_MAX_BUF];
const unsigned char *apdu_p;
size_t apdu_part_len;
size_t msglen;
unsigned char seqno;
int bwi = 0;
unsigned char chain = 0;
if (apdu_len == 0 || apdu_len > sizeof (msg) - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
apdu_p = apdu_buf;
while (1)
{
apdu_part_len = apdu_len;
if (apdu_part_len > handle->max_ccid_msglen - 10)
{
apdu_part_len = handle->max_ccid_msglen - 10;
chain |= 0x01;
}
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = chain;
msg[9] = 0;
memcpy (msg+10, apdu_p, apdu_part_len);
set_msg_len (msg, apdu_part_len);
msglen = 10 + apdu_part_len;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
apdu_p += apdu_part_len;
apdu_len -= apdu_part_len;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
if (!(chain & 0x01))
break;
chain = 0x02;
}
apdu_len = 0;
while (1)
{
apdu_part_len = msglen - 10;
if (resp && apdu_len + apdu_part_len <= maxresplen)
memcpy (resp + apdu_len, msg+10, apdu_part_len);
apdu_len += apdu_part_len;
if (!(msg[9] & 0x01))
break;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = 0x10; /* Request next data block */
msg[9] = 0;
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
}
if (resp)
{
if (apdu_len > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)apdu_len, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
*nresp = apdu_len;
}
return 0;
}
/*
Protocol T=1 overview
Block Structure:
Prologue Field:
1 byte Node Address (NAD)
1 byte Protocol Control Byte (PCB)
1 byte Length (LEN)
Information Field:
0-254 byte APDU or Control Information (INF)
Epilogue Field:
1 byte Error Detection Code (EDC)
NAD:
bit 7 unused
bit 4..6 Destination Node Address (DAD)
bit 3 unused
bit 2..0 Source Node Address (SAD)
If node addresses are not used, SAD and DAD should be set to 0 on
the first block sent to the card. If they are used they should
have different values (0 for one is okay); that first block sets up
the addresses of the nodes.
PCB:
Information Block (I-Block):
bit 7 0
bit 6 Sequence number (yep, that is modulo 2)
bit 5 Chaining flag
bit 4..0 reserved
Received-Ready Block (R-Block):
bit 7 1
bit 6 0
bit 5 0
bit 4 Sequence number
bit 3..0 0 = no error
1 = EDC or parity error
2 = other error
other values are reserved
Supervisory Block (S-Block):
bit 7 1
bit 6 1
bit 5 clear=request,set=response
bit 4..0 0 = resynchronization request
1 = information field size request
2 = abort request
3 = extension of BWT request
4 = VPP error
other values are reserved
*/
int
ccid_transceive (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
/* The size of the buffer used to be 10+259. For the via_escape
hack we need one extra byte, thus 11+259. */
unsigned char send_buffer[11+259], recv_buffer[11+259];
const unsigned char *apdu;
size_t apdulen;
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, last_tpdulen, n;
unsigned char seqno;
unsigned int edc;
int use_crc = 0;
int hdrlen, pcboff;
size_t dummy_nresp;
int via_escape = 0;
int next_chunk = 1;
int sending = 1;
int retries = 0;
int resyncing = 0;
int nad_byte;
int wait_more = 0;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
/* Smarter readers allow sending APDUs directly; divert here. */
if (handle->apdu_level)
{
/* We employ a hack for Omnikey readers which are able to send
TPDUs using an escape sequence. There is no documentation
but the Windows driver does it this way. Tested using a
CM6121. This method works also for the Cherry XX44
keyboards; however there are problems with the
ccid_transceive_secure which leads to a loss of sync on the
CCID level. If Cherry wants to make their keyboard work
again, they should hand over some docs. */
if ((handle->id_vendor == VENDOR_OMNIKEY)
&& handle->apdu_level < 2
&& is_exlen_apdu (apdu_buf, apdu_buflen))
via_escape = 1;
else
return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen,
resp, maxresplen, nresp);
}
/* The other readers we support require sending TPDUs. */
tpdulen = 0; /* Avoid compiler warning about no initialization. */
msg = send_buffer;
hdrlen = via_escape? 11 : 10;
/* NAD: DAD=1, SAD=0 */
nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0;
if (via_escape)
nad_byte = 0;
last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */
for (;;)
{
if (next_chunk)
{
next_chunk = 0;
apdu = apdu_buf;
apdulen = apdu_buflen;
assert (apdulen);
/* Construct an I-Block. */
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
if (apdulen > handle->ifsc )
{
apdulen = handle->ifsc;
apdu_buf += handle->ifsc;
apdu_buflen -= handle->ifsc;
tpdu[1] |= (1 << 5); /* Set more bit. */
}
tpdu[2] = apdulen;
memcpy (tpdu+3, apdu, apdulen);
tpdulen = 3 + apdulen;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
if (via_escape)
{
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = 0x1a; /* Omnikey command to send a TPDU. */
set_msg_len (msg, 1 + tpdulen);
}
else
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = wait_more; /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
}
msglen = hdrlen + tpdulen;
if (!resyncing)
last_tpdulen = tpdulen;
pcboff = hdrlen+1;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, seqno,
(wait_more ? wait_more : 1) * CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
tpdu = msg + hdrlen;
tpdulen = msglen - hdrlen;
resyncing = 0;
if (tpdulen < 4)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
#ifdef USE_NPTH
npth_protect ();
#endif
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0,
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
wait_more = 0;
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
retries = 0;
if (sending)
{ /* last block sent was successful. */
handle->t1_ns ^= 1;
sending = 0;
}
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
continue;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
resp += n;
*nresp += n;
maxresplen -= n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{
retries++;
if (via_escape && retries == 1 && (msg[pcboff] & 0x0f))
{
/* Error probably due to switching to TPDU. Send a
resync request. We use the recv_buffer so that
we don't corrupt the send_buffer. */
msg = recv_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = 0xc0; /* S-block resync request. */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
resyncing = 1;
DEBUGOUT ("T=1: requesting resync\n");
}
else if (retries > 3)
{
DEBUGOUT ("T=1: 3 failed retries\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{
/* Error: repeat last block */
msg = send_buffer;
tpdulen = last_tpdulen;
}
}
else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (sending)
{ /* Send next chunk. */
retries = 0;
msg = send_buffer;
next_chunk = 1;
handle->t1_ns ^= 1;
}
else
{
DEBUGOUT ("unexpected ACK R-block received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
retries = 0;
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1)
{
/* Information field size request. */
unsigned char ifsc = tpdu[3];
if (ifsc < 16 || ifsc > 254)
return CCID_DRIVER_ERR_CARD_IO_ERROR;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */
tpdu[2] = 1;
tpdu[3] = ifsc;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc);
}
else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
{
/* Wait time extension request. */
unsigned char bwi = tpdu[3];
wait_more = bwi;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
tpdu[2] = 1;
tpdu[3] = bwi;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi);
print_progress (handle);
}
else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2])
{
DEBUGOUT ("T=1: resync ack from reader\n");
/* Repeat previous block. */
msg = send_buffer;
tpdulen = last_tpdulen;
}
else
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
} /* end T=1 protocol loop. */
return 0;
}
/* Send the CCID Secure command to the reader. APDU_BUF should
contain the APDU template. PIN_MODE defines how the pin gets
formatted:
1 := The PIN is ASCII encoded and of variable length. The
length of the PIN entered will be put into Lc by the reader.
The APDU should me made up of 4 bytes without Lc.
PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0
may be used t enable reasonable defaults.
When called with RESP and NRESP set to NULL, the function will
merely check whether the reader supports the secure command for the
given APDU and PIN_MODE. */
int
ccid_transceive_secure (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
pininfo_t *pininfo,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
unsigned char send_buffer[10+259], recv_buffer[10+259];
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, n;
unsigned char seqno;
size_t dummy_nresp;
int testmode;
int cherry_mode = 0;
int add_zero = 0;
int enable_varlen = 0;
testmode = !resp && !nresp;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
;
else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
;
else
return CCID_DRIVER_ERR_NO_PINPAD;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
/* Note that the 25 is the maximum value the SPR532 allows. */
if (pininfo->minlen < 1 || pininfo->minlen > 25
|| pininfo->maxlen < 1 || pininfo->maxlen > 25
|| pininfo->minlen > pininfo->maxlen)
return CCID_DRIVER_ERR_INV_VALUE;
/* We have only tested a few readers so better don't risk anything
and do not allow the use with other readers. */
switch (handle->id_vendor)
{
case VENDOR_SCM: /* Tested with SPR 532. */
case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */
case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */
pininfo->maxlen = 25;
enable_varlen = 1;
break;
case VENDOR_REINER:/* Tested with cyberJack go */
case VENDOR_VASCO: /* Tested with DIGIPASS 920 */
enable_varlen = 1;
break;
case VENDOR_CHERRY:
pininfo->maxlen = 15;
enable_varlen = 1;
/* The CHERRY XX44 keyboard echos an asterisk for each entered
character on the keyboard channel. We use a special variant
of PC_to_RDR_Secure which directs these characters to the
smart card's bulk-in channel. We also need to append a zero
Lc byte to the APDU. It seems that it will be replaced with
the actual length instead of being appended before the APDU
is send to the card. */
add_zero = 1;
if (handle->id_product != CHERRY_ST2000)
cherry_mode = 1;
break;
case VENDOR_NXP:
if (handle->id_product == CRYPTOUCAN)
{
pininfo->maxlen = 25;
enable_varlen = 1;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
case VENDOR_GEMPC:
if (handle->id_product == GEMPC_PINPAD)
{
enable_varlen = 0;
pininfo->minlen = 4;
pininfo->maxlen = 8;
break;
}
else if (handle->id_product == GEMPC_EZIO)
{
pininfo->maxlen = 25;
enable_varlen = 1;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
default:
if ((handle->id_vendor == VENDOR_VEGA &&
handle->id_product == VEGA_ALPHA))
{
enable_varlen = 0;
pininfo->minlen = 4;
pininfo->maxlen = 8;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
}
if (enable_varlen)
pininfo->fixedlen = 0;
if (testmode)
return 0; /* Success */
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return CCID_DRIVER_ERR_NOT_SUPPORTED;
msg = send_buffer;
if (handle->id_vendor == VENDOR_SCM)
{
DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
rc = send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3,
NULL, 0, NULL);
if (rc)
return rc;
}
msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = apdu_buf[1] == 0x20 ? 0 : 1;
/* Perform PIN verification or PIN modification. */
msg[11] = 0; /* Timeout in seconds. */
msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
if (handle->id_vendor == VENDOR_SCM)
{
/* For the SPR532 the next 2 bytes need to be zero. We do this
for all SCM products. Kudos to Martin Paljak for this
hint. */
msg[13] = msg[14] = 0;
}
else
{
msg[13] = pininfo->fixedlen; /* bmPINBlockString:
0 bits of pin length to insert.
PIN block size by fixedlen. */
msg[14] = 0x00; /* bmPINLengthFormat:
Units are bytes, position is 0. */
}
msglen = 15;
if (apdu_buf[1] == 0x24)
{
msg[msglen++] = 0; /* bInsertionOffsetOld */
msg[msglen++] = pininfo->fixedlen; /* bInsertionOffsetNew */
}
/* The following is a little endian word. */
msg[msglen++] = pininfo->maxlen; /* wPINMaxExtraDigit-Maximum. */
msg[msglen++] = pininfo->minlen; /* wPINMaxExtraDigit-Minimum. */
if (apdu_buf[1] == 0x24)
msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01;
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
msg[msglen] = 0x02; /* bEntryValidationCondition:
Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
msg[msglen] |= 0x01; /* Max size reached. */
msglen++;
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0x01; /* bNumberMessage. */
else
msg[msglen++] = 0x03; /* bNumberMessage. */
msg[msglen++] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */
msg[msglen++] = 0x04; /* wLangId-High. */
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0; /* bMsgIndex. */
else
{
msg[msglen++] = 0; /* bMsgIndex1. */
msg[msglen++] = 1; /* bMsgIndex2. */
msg[msglen++] = 2; /* bMsgIndex3. */
}
/* Calculate Lc. */
n = pininfo->fixedlen;
if (apdu_buf[1] == 0x24)
n += pininfo->fixedlen;
/* bTeoProlog follows: */
msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0;
msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */
if (n)
msg[msglen++] = n + 5; /* apdulen should be filled for fixed length. */
else
msg[msglen++] = 0; /* The apdulen will be filled in by the reader. */
/* APDU follows: */
msg[msglen++] = apdu_buf[0]; /* CLA */
msg[msglen++] = apdu_buf[1]; /* INS */
msg[msglen++] = apdu_buf[2]; /* P1 */
msg[msglen++] = apdu_buf[3]; /* P2 */
if (add_zero)
msg[msglen++] = 0;
else if (pininfo->fixedlen != 0)
{
msg[msglen++] = n;
memset (&msg[msglen], 0xff, n);
msglen += n;
}
/* An EDC is not required. */
set_msg_len (msg, msglen - 10);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
RDR_to_PC_DataBlock, seqno, 30000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (handle->apdu_level)
{
if (resp)
{
if (tpdulen > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)tpdulen, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, tpdu, tpdulen);
*nresp = tpdulen;
}
return 0;
}
if (tpdulen < 4)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
#ifdef USE_NPTH
npth_protect ();
#endif
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
/* Last block sent was successful. */
handle->t1_ns ^= 1;
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
DEBUGOUT ("I-block with wrong seqno received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
*nresp += n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
DEBUGOUT ("chaining requested but not supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{ /* Error: repeat last block */
DEBUGOUT ("No retries supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (!!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{ /* Send next chunk. */
DEBUGOUT ("chaining not supported on Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
return 0;
}
#ifdef TEST
static void
print_error (int err)
{
const char *p;
char buf[50];
switch (err)
{
case 0: p = "success"; break;
case CCID_DRIVER_ERR_OUT_OF_CORE: p = "out of core"; break;
case CCID_DRIVER_ERR_INV_VALUE: p = "invalid value"; break;
case CCID_DRIVER_ERR_NO_DRIVER: p = "no driver"; break;
case CCID_DRIVER_ERR_NOT_SUPPORTED: p = "not supported"; break;
case CCID_DRIVER_ERR_LOCKING_FAILED: p = "locking failed"; break;
case CCID_DRIVER_ERR_BUSY: p = "busy"; break;
case CCID_DRIVER_ERR_NO_CARD: p = "no card"; break;
case CCID_DRIVER_ERR_CARD_INACTIVE: p = "card inactive"; break;
case CCID_DRIVER_ERR_CARD_IO_ERROR: p = "card I/O error"; break;
case CCID_DRIVER_ERR_GENERAL_ERROR: p = "general error"; break;
case CCID_DRIVER_ERR_NO_READER: p = "no reader"; break;
case CCID_DRIVER_ERR_ABORTED: p = "aborted"; break;
default: sprintf (buf, "0x%05x", err); p = buf; break;
}
fprintf (stderr, "operation failed: %s\n", p);
}
static void
print_data (const unsigned char *data, size_t length)
{
if (length >= 2)
{
fprintf (stderr, "operation status: %02X%02X\n",
data[length-2], data[length-1]);
length -= 2;
}
if (length)
{
fputs (" returned data:", stderr);
for (; length; length--, data++)
fprintf (stderr, " %02X", *data);
putc ('\n', stderr);
}
}
static void
print_result (int rc, const unsigned char *data, size_t length)
{
if (rc)
print_error (rc);
else if (data)
print_data (data, length);
}
int
main (int argc, char **argv)
{
gpg_error_t err;
ccid_driver_t ccid;
int slotstat;
unsigned char result[512];
size_t resultlen;
int no_pinpad = 0;
int verify_123456 = 0;
int did_verify = 0;
int no_poll = 0;
int idx_max;
struct ccid_dev_table *ccid_table;
if (argc)
{
argc--;
argv++;
}
while (argc)
{
if ( !strcmp (*argv, "--list"))
{
char *p;
p = ccid_get_reader_list ();
if (!p)
return 1;
fputs (p, stderr);
free (p);
return 0;
}
else if ( !strcmp (*argv, "--debug"))
{
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-poll"))
{
no_poll = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-pinpad"))
{
no_pinpad = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--verify-123456"))
{
verify_123456 = 1;
argc--; argv++;
}
else
break;
}
err = ccid_dev_scan (&idx_max, &ccid_table);
if (err)
return 1;
if (idx_max == 0)
return 1;
err = ccid_open_reader (argc? *argv:NULL, 0, ccid_table, &ccid, NULL);
if (err)
return 1;
ccid_dev_scan_finish (ccid_table, idx_max);
if (!no_poll)
ccid_poll (ccid);
fputs ("getting ATR ...\n", stderr);
err = ccid_get_atr (ccid, NULL, 0, NULL);
if (err)
{
print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting slot status ...\n", stderr);
err = ccid_slot_status (ccid, &slotstat, 1);
if (err)
{
print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("selecting application OpenPGP ....\n", stderr);
{
static unsigned char apdu[] = {
0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01};
err = ccid_transceive (ccid,
apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting OpenPGP DO 0x65 ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 };
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
if (!no_pinpad)
{
}
if (!no_pinpad)
{
static unsigned char apdu[] = { 0, 0x20, 0, 0x81 };
pininfo_t pininfo = { 0, 0, 0 };
if (ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
NULL, 0, NULL))
fputs ("can't verify using a PIN-Pad reader\n", stderr);
else
{
fputs ("verifying CHV1 using the PINPad ....\n", stderr);
err = ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
did_verify = 1;
}
}
if (verify_123456 && !did_verify)
{
fputs ("verifying that CHV1 is 123456....\n", stderr);
{
static unsigned char apdu[] = {0, 0x20, 0, 0x81,
6, '1','2','3','4','5','6'};
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
}
if (!err)
{
fputs ("getting OpenPGP DO 0x5E ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 };
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
}
ccid_close_reader (ccid);
return 0;
}
/*
* Local Variables:
* compile-command: "gcc -DTEST -DGPGRT_ENABLE_ES_MACROS -DHAVE_NPTH -DUSE_NPTH -Wall -I/usr/include/libusb-1.0 -I/usr/local/include -lusb-1.0 -g ccid-driver.c -lnpth -lgpg-error"
* End:
*/
#endif /*TEST*/
#endif /*HAVE_LIBUSB*/
diff --git a/scd/command.c b/scd/command.c
index c3ca93846..cd31218e8 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -1,2745 +1,2745 @@
/* command.c - SCdaemon command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2007, 2008, 2009, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "scdaemon.h"
#include <assuan.h>
#include <ksba.h>
#include "iso7816.h"
#include "apdu.h" /* Required for apdu_*_reader (). */
#include "atr.h"
#ifdef HAVE_LIBUSB
#include "ccid-driver.h"
#endif
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That
* length needs to small compared to the maximum Assuan line length. */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
/* Maximum allowed size for "SETATTR --inquire". */
#define MAXLEN_SETATTRDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local)
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
void *event_signal; /* Or NULL if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
/* If a session has been locked we store a link to its server object
in this variable. */
static struct server_local_s *locked_session;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Convert the STRING into a newly allocated buffer while translating
the hex numbers. Stops at the first invalid character. Blanks and
colons are allowed to separate the hex digits. Returns NULL on
error or a newly malloced buffer and its length in LENGTH. */
static unsigned char *
hex_to_buffer (const char *string, size_t *r_length)
{
unsigned char *buffer;
const char *s;
size_t n;
buffer = xtrymalloc (strlen (string)+1);
if (!buffer)
return NULL;
for (s=string, n=0; *s; s++)
{
if (spacep (s) || *s == ':')
continue;
if (hexdigitp (s) && hexdigitp (s+1))
{
buffer[n++] = xtoi_2 (s);
s++;
}
else
break;
}
*r_length = n;
return buffer;
}
/* Reset the card and free the application context. With SEND_RESET
set to true actually send a RESET to the reader; this is the normal
way of calling the function. */
static void
do_reset (ctrl_t ctrl, int send_reset)
{
card_t card = ctrl->card_ctx;
if (card)
card_reset (card, ctrl, IS_LOCKED (ctrl)? 0: send_reset);
/* If we hold a lock, unlock now. */
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESET\n");
}
}
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
do_reset (ctrl, 1);
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
#ifdef _WIN64
ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16);
#else
ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16);
#endif
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
/* If the card has not yet been opened, do it. */
static gpg_error_t
open_card (ctrl_t ctrl)
{
/* If we ever got a card not present error code, return that. Only
the SERIALNO command and a reset are able to clear from that
state. */
if (ctrl->server_local->card_removed)
return gpg_error (GPG_ERR_CARD_REMOVED);
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if (ctrl->card_ctx)
return 0;
return select_application (ctrl, NULL, &ctrl->card_ctx, 0, NULL, 0);
}
/* Explicitly open a card for a specific use of APPTYPE or SERIALNO.
- * If OPT_ALL ist set also add all possible additional apps. */
+ * If OPT_ALL is set also add all possible additional apps. */
static gpg_error_t
open_card_with_request (ctrl_t ctrl,
const char *apptypestr, const char *serialno,
int opt_all)
{
gpg_error_t err;
unsigned char *serialno_bin = NULL;
size_t serialno_bin_len = 0;
card_t card = ctrl->card_ctx;
if (serialno)
serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
/* If we are already initialized for one specific application we
need to check that the client didn't requested a specific
application different from the one in use before we continue. */
if (apptypestr && ctrl->card_ctx)
{
err = check_application_conflict (ctrl->card_ctx, apptypestr,
serialno_bin, serialno_bin_len);
if (gpg_err_code (err) == GPG_ERR_FALSE)
{
/* Different application but switching is supported. */
err = select_additional_application (ctrl, apptypestr);
}
goto leave;
}
/* Re-scan USB devices. Release CARD, before the scan. */
/* FIXME: Is a card_unref sufficient or do we need to deallocate? */
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref (card);
err = select_application (ctrl, apptypestr, &ctrl->card_ctx, 1,
serialno_bin, serialno_bin_len);
if (!err && opt_all)
err = select_additional_application (ctrl, NULL);
leave:
xfree (serialno_bin);
return err;
}
static const char hlp_serialno[] =
"SERIALNO [--demand=<serialno>] [--all] [<apptype>]\n"
"\n"
"Return the serial number of the card using a status response. This\n"
"function should be used to check for the presence of a card.\n"
"\n"
"If --demand is given, an application on the card with SERIALNO is\n"
"selected and an error is returned if no such card available.\n"
"\n"
"If --all is given, all possible other applications of the card are\n"
"also selected to prepare for things like \"LEARN --force --multi\".\n"
"\n"
"If APPTYPE is given, an application of that type is selected and an\n"
"error is returned if the application is not supported or available.\n"
"The default is to auto-select the application using a hardwired\n"
"preference system.\n"
"\n"
"This function is special in that it can be used to reset the card.\n"
"Most other functions will return an error when a card change has\n"
"been detected and the use of this function is therefore required.\n"
"\n"
"Background: We want to keep the client clear of handling card\n"
"changes between operations; i.e. the client can assume that all\n"
"operations are done on the same card unless he calls this function.";
static gpg_error_t
cmd_serialno (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
struct server_local_s *sl;
int rc = 0;
char *serial;
const char *demand;
int opt_all = has_option (line, "--all");
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if ((demand = has_option_name (line, "--demand")))
{
if (*demand != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
line = (char *)++demand;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
}
else
demand = NULL;
line = skip_options (line);
/* Clear the remove flag so that the open_card is able to reread it. */
if (ctrl->server_local->card_removed)
ctrl->server_local->card_removed = 0;
if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand, opt_all)))
{
ctrl->server_local->card_removed = 1;
return rc;
}
/* Success, clear the card_removed flag for all sessions. */
for (sl=session_list; sl; sl = sl->next_session)
{
ctrl_t c = sl->ctrl_backlink;
if (c != ctrl)
c->server_local->card_removed = 0;
}
serial = card_get_serialno (ctrl->card_ctx);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
xfree (serial);
return rc;
}
static const char hlp_switchcard[] =
"SWITCHCARD [<serialno>]\n"
"\n"
"Make the card with SERIALNO the current card.\n"
"The command \"getinfo card_list\" can be used to list\n"
"the serial numbers of inserted and known cards. Note\n"
"that the command \"SERIALNO\" can be used to refresh\n"
"the list of known cards. A simple SERIALNO status\n"
"is printed on success.";
static gpg_error_t
cmd_switchcard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *sn_bin = NULL;
size_t sn_bin_len = 0;
if ((err = open_card (ctrl)))
return err;
line = skip_options (line);
if (*line)
{
sn_bin = hex_to_buffer (line, &sn_bin_len);
if (!sn_bin)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Note that an SN_BIN of NULL will only print the status. */
err = app_switch_current_card (ctrl, sn_bin, sn_bin_len);
leave:
xfree (sn_bin);
return err;
}
static const char hlp_switchapp[] =
"SWITCHAPP [<appname>]\n"
"\n"
"Make APPNAME the active application for the current card.\n"
"Only some cards support switching between application; the\n"
"command \"getinfo active_app\" can be used to get a list of\n"
"applications which can be switched to. A SERIALNO status\n"
"including the active appname is printed on success.";
static gpg_error_t
cmd_switchapp (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if ((err = open_card (ctrl)))
return err;
line = skip_options (line);
return app_switch_active_app (ctrl->card_ctx, ctrl, line);
}
static const char hlp_learn[] =
"LEARN [--force] [--keypairinfo] [--multi]\n"
"\n"
"Learn all useful information of the currently inserted card. When\n"
"used without the force options, the command might do an INQUIRE\n"
"like this:\n"
"\n"
" INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n"
"\n"
"The client should just send an \"END\" if the processing should go on\n"
"or a \"CANCEL\" to force the function to terminate with a Cancel\n"
"error message.\n"
"\n"
"With the option --keypairinfo only KEYPARIINFO status lines are\n"
"returned.\n"
"\n"
"The response of this command is a list of status lines formatted as\n"
"this:\n"
"\n"
" S APPTYPE <apptype>\n"
"\n"
"This returns the type of the application, currently the strings:\n"
"\n"
" P15 = PKCS-15 structure used\n"
" DINSIG = DIN SIG\n"
" OPENPGP = OpenPGP card\n"
" PIV = PIV card\n"
" NKS = NetKey card\n"
"\n"
"are implemented. These strings are aliases for the AID. With option\n"
"--multi information for all switchable apps are returned.\n"
"\n"
" S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id> [<usage>]\n"
"\n"
"If there is no certificate yet stored on the card a single 'X' is\n"
"returned as the keygrip. For more info see doc/DETAILS. In addition\n"
"to the keypair info, information about all certificates stored on the\n"
"card is also returned:\n"
"\n"
" S CERTINFO <certtype> <hexstring_with_id>\n"
"\n"
"Where CERTTYPE is a number indicating the type of certificate:\n"
" 0 := Unknown\n"
" 100 := Regular X.509 cert\n"
" 101 := Trusted X.509 cert\n"
" 102 := Useful X.509 cert\n"
" 110 := Root CA cert in a special format (e.g. DINSIG)\n"
" 111 := Root CA cert as standard X509 cert.\n"
"\n"
"For certain cards, more information will be returned:\n"
"\n"
" S KEY-FPR <no> <hexstring>\n"
"\n"
"For OpenPGP cards this returns the stored fingerprints of the\n"
"keys. This can be used check whether a key is available on the\n"
"card. NO may be 1, 2 or 3.\n"
"\n"
" S CA-FPR <no> <hexstring>\n"
"\n"
"Similar to above, these are the fingerprints of keys assumed to be\n"
"ultimately trusted.\n"
"\n"
" S DISP-NAME <name_of_card_holder>\n"
"\n"
"The name of the card holder as stored on the card; percent\n"
"escaping takes place, spaces are encoded as '+'\n"
"\n"
" S PUBKEY-URL <url>\n"
"\n"
"The URL to be used for locating the entire public key.\n"
" \n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
int only_keypairinfo = has_option (line, "--keypairinfo");
int opt_multi = has_option (line, "--multi");
if ((rc = open_card (ctrl)))
return rc;
/* Unless the force option is used we try a shortcut by identifying
the card using a serial number and inquiring the client with
that. The client may choose to cancel the operation if he already
knows about this card */
if (!only_keypairinfo)
{
const char *reader;
char *serial;
card_t card = ctrl->card_ctx;
if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
reader = apdu_get_reader_name (card->slot);
if (!reader)
return out_of_core ();
send_status_direct (ctrl, "READER", reader);
/* No need to free the string of READER. */
serial = card_get_serialno (ctrl->card_ctx);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
rc = assuan_write_status (ctx, "SERIALNO", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
if (!has_option (line, "--force"))
{
char *command;
rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial);
if (rc < 0)
{
xfree (serial);
return out_of_core ();
}
rc = assuan_inquire (ctx, command, NULL, NULL, 0);
xfree (command);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED)
log_error ("inquire KNOWNCARDP failed: %s\n",
gpg_strerror (rc));
xfree (serial);
return rc;
}
/* Not canceled, so we have to proceed. */
}
xfree (serial);
}
/* Let the application print out its collection of useful status
information. */
if (!rc)
rc = app_write_learn_status
(ctrl->card_ctx, ctrl,
( (only_keypairinfo? APP_LEARN_FLAG_KEYPAIRINFO : 0)
| (opt_multi? APP_LEARN_FLAG_MULTI : 0)) );
return rc;
}
static const char hlp_readcert[] =
"READCERT <hexified_certid>|<keyid>|<oid>\n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_readcert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *cert;
size_t ncert;
if ((rc = open_card (ctrl)))
return rc;
line = xtrystrdup (line); /* Need a copy of the line. */
if (!line)
return gpg_error_from_syserror ();
rc = app_readcert (ctrl->card_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
xfree (line);
line = NULL;
if (!rc)
{
rc = assuan_send_data (ctx, cert, ncert);
xfree (cert);
if (rc)
return rc;
}
return rc;
}
static gpg_error_t
do_readkey (card_t card, ctrl_t ctrl, const char *line,
int opt_info, int opt_nokey,
unsigned char **pk_p, size_t *pklen_p)
{
int rc;
/* If the application supports the READKEY function we use that.
Otherwise we use the old way by extracting it from the
certificate. */
rc = app_readkey (card, ctrl, line,
opt_info? APP_READKEY_FLAG_INFO : 0,
opt_nokey? NULL : pk_p, pklen_p);
if (!rc)
/* Okay, got that key. */
return 0;
if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
unsigned char *cert = NULL;
size_t ncert;
/* Fall back to certificate reading. */
rc = app_readcert (card, ctrl, line, &cert, &ncert);
if (rc)
{
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
return rc;
}
rc = app_help_pubkey_from_cert (cert, ncert, pk_p, pklen_p);
xfree (cert);
if (rc)
{
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (rc));
return rc;
}
if (opt_info)
{
char keygripstr[KEYGRIP_LEN*2+1];
rc = app_help_get_keygrip_string_pk (*pk_p, *pklen_p, keygripstr);
if (rc)
{
log_error ("app_help_get_keygrip_string failed: %s\n",
gpg_strerror (rc));
return rc;
}
/* FIXME: Using LINE is not correct because it might be an
* OID and has not been canonicalized (i.e. uppercased). */
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
line, strlen (line),
NULL, (size_t)0);
}
}
else
log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_readkey[] =
"READKEY [--advanced] [--info[-only]] <keyid>|<oid>|<keygrip>\n"
"\n"
"Return the public key for the given cert or key ID as a standard\n"
"S-expression. With --advanced the S-expression is returned in\n"
"advanced format. With --info a KEYPAIRINFO status line is also\n"
"emitted; with --info-only the regular output is suppressed.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
int advanced = 0;
int opt_info = 0;
int opt_nokey = 0;
unsigned char *pk = NULL;
size_t pklen;
card_t card;
int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
if (has_option (line, "--advanced"))
advanced = 1;
if (has_option (line, "--info"))
opt_info = 1;
if (has_option (line, "--info-only"))
opt_info = opt_nokey = 1;
line = skip_options (line);
line = xtrystrdup (line); /* Need a copy of the line. */
if (!line)
return gpg_error_from_syserror ();
if (strlen (line) == 40)
{
card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, line, 0);
direct = 1;
}
else
card = ctrl->card_ctx;
if (card)
{
if (direct)
card_ref (card);
rc = do_readkey (card, ctrl, line, opt_info, opt_nokey, &pk, &pklen);
if (direct)
card_unref (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
if (opt_nokey)
;
else if (advanced)
{
gcry_sexp_t s_key;
unsigned char *pkadv;
size_t pkadvlen;
rc = gcry_sexp_new (&s_key, pk, pklen, 0);
if (rc)
goto leave;
pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0);
pkadv = xtrymalloc (pkadvlen);
if (!pkadv)
{
rc = gpg_error_from_syserror ();
goto leave;
}
log_assert (pkadvlen);
gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen);
gcry_sexp_release (s_key);
/* (One less to adjust for the trailing '\0') */
rc = assuan_send_data (ctx, pkadv, pkadvlen-1);
xfree (pkadv);
}
else
rc = assuan_send_data (ctx, pk, pklen);
leave:
xfree (pk);
xfree (line);
return rc;
}
static const char hlp_setdata[] =
"SETDATA [--append] <hexstring>\n"
"\n"
"The client should use this command to tell us the data he want to sign.\n"
"With the option --append, the data is appended to the data set by a\n"
"previous SETDATA command.";
static gpg_error_t
cmd_setdata (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int append;
int n, i, off;
char *p;
unsigned char *buf;
append = (ctrl->in_data.value && has_option (line, "--append"));
line = skip_options (line);
if (locked_session && locked_session != ctrl->server_local)
return gpg_error (GPG_ERR_LOCKED);
/* Parse the hexstring. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if (!n)
return set_error (GPG_ERR_ASS_PARAMETER, "no data given");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
n /= 2;
if (append)
{
if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA)
return set_error (GPG_ERR_TOO_LARGE,
"limit on total size of data reached");
buf = xtrymalloc (ctrl->in_data.valuelen + n);
}
else
buf = xtrymalloc (n);
if (!buf)
return out_of_core ();
if (append)
{
memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen);
off = ctrl->in_data.valuelen;
}
else
off = 0;
for (p=line, i=0; i < n; p += 2, i++)
buf[off+i] = xtoi_2 (p);
xfree (ctrl->in_data.value);
ctrl->in_data.value = buf;
ctrl->in_data.valuelen = off+n;
return 0;
}
static gpg_error_t
pin_cb (void *opaque, const char *info, char **retstr)
{
assuan_context_t ctx = opaque;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
if (!retstr)
{
/* We prompt for pinpad entry. To make sure that the popup has
been show we use an inquire and not just a status message.
We ignore any value returned. */
if (info)
{
log_debug ("prompting for pinpad entry '%s'\n", info);
rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
}
else
{
log_debug ("dismiss pinpad entry prompt\n");
rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT",
&value, &valuelen, MAXLEN_PIN);
}
if (!rc)
xfree (value);
return rc;
}
*retstr = NULL;
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
if (rc)
return rc;
if (!valuelen || value[valuelen-1])
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] <hexified_id>\n"
"\n"
"The --hash option is optional; the default is SHA1.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
int hash_algo;
card_t card;
int direct = 0;
if (has_option (line, "--hash=rmd160"))
hash_algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=sha1"))
hash_algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
hash_algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
hash_algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
hash_algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
hash_algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=md5"))
hash_algo = GCRY_MD_MD5;
else if (!strstr (line, "--"))
hash_algo = GCRY_MD_SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use the card, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
{
card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr, 0);
direct = 1;
}
else
card = ctrl->card_ctx;
if (card)
{
if (direct)
card_ref (card);
rc = app_sign (card, ctrl,
keyidstr, hash_algo,
pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
if (direct)
card_unref (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_sign failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkauth[] =
"PKAUTH <hexified_id>";
static gpg_error_t
cmd_pkauth (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
card_t card;
int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use CARD, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
{
card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr, 0);
direct = 1;
}
else
card = ctrl->card_ctx;
if (card)
{
if (direct)
card_ref (card);
rc = app_auth (card, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
if (direct)
card_unref (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_auth failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT <hexified_id>";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
unsigned int infoflags;
card_t card;
int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use CARD, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
{
card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keyidstr, 0);
direct = 1;
}
else
card = ctrl->card_ctx;
if (card)
{
if (direct)
card_ref (card);
rc = app_decipher (card, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen, &infoflags);
if (direct)
card_unref (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_decipher failed: %s\n", gpg_strerror (rc));
}
else
{
/* If the card driver told us that there is no padding, send a
status line. If there is a padding it is assumed that the
caller knows what padding is used. It would have been better
to always send that information but for backward
compatibility we can't do that. */
if ((infoflags & APP_DECIPHER_INFO_NOPAD))
send_status_direct (ctrl, "PADDING", "0");
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_getattr[] =
"GETATTR <name> [<keygrip>]\n"
"\n"
"This command is used to retrieve data from a smartcard. The\n"
"allowed names depend on the currently selected smartcard\n"
"application. NAME must be percent and '+' escaped. The value is\n"
"returned through status message, see the LEARN command for details.\n"
"\n"
"However, the current implementation assumes that Name is not escaped;\n"
"this works as long as no one uses arbitrary escaping. \n"
"\n"
"Note, that this function may even be used on a locked card.\n"
"When KEYGRIP is specified, it accesses directly with the KEYGRIP.";
static gpg_error_t
cmd_getattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
const char *keyword;
card_t card;
int direct = 0;
if ((rc = open_card (ctrl)))
return rc;
keyword = line;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
if (strlen (line) == 40)
{
card = app_do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, line, 0);
direct = 1;
}
else
card = ctrl->card_ctx;
if (card)
{
if (direct)
card_ref (card);
/* FIXME: Applications should not return sensitive data if the card
is locked. */
rc = app_getattr (card, ctrl, keyword);
if (direct)
card_unref (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
return rc;
}
static const char hlp_setattr[] =
"SETATTR [--inquire] <name> <value> \n"
"\n"
"This command is used to store data on a smartcard. The allowed\n"
"names and values are depend on the currently selected smartcard\n"
"application. NAME and VALUE must be percent and '+' escaped.\n"
"\n"
"However, the current implementation assumes that NAME is not\n"
"escaped; this works as long as no one uses arbitrary escaping.\n"
"\n"
"If the option --inquire is used, VALUE shall not be given; instead\n"
"an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n"
"value is in this case considered to be confidential and not logged.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"setattr function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_setattr (assuan_context_t ctx, char *orig_line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyword;
int keywordlen;
size_t nbytes;
char *line, *linebuf;
int opt_inquire;
opt_inquire = has_option (orig_line, "--inquire");
orig_line = skip_options (orig_line);
if ((err = open_card (ctrl)))
return err;
/* We need to use a copy of LINE, because PIN_CB uses the same
context and thus reuses the Assuan provided LINE. */
line = linebuf = xtrystrdup (orig_line);
if (!line)
return out_of_core ();
keyword = line;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
if (*line)
*line++ = 0;
while (spacep (line))
line++;
if (opt_inquire)
{
unsigned char *value;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA);
assuan_end_confidential (ctx);
if (!err)
{
err = app_setattr (ctrl->card_ctx, ctrl, keyword, pin_cb, ctx,
value, nbytes);
wipememory (value, nbytes);
xfree (value);
}
}
else
{
nbytes = percent_plus_unescape_inplace (line, 0);
err = app_setattr (ctrl->card_ctx, ctrl, keyword, pin_cb, ctx,
(const unsigned char*)line, nbytes);
}
xfree (linebuf);
return err;
}
static const char hlp_writecert[] =
"WRITECERT <hexified_certid>\n"
"\n"
"This command is used to store a certificate on a smartcard. The\n"
"allowed certids depend on the currently selected smartcard\n"
"application. The actual certifciate is requested using the inquiry\n"
"\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n"
"\n"
"In almost all cases a PIN will be requested. See the related\n"
"writecert function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *certid;
unsigned char *certdata;
size_t certdatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no certid given");
certid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
certid = xtrystrdup (certid);
if (!certid)
return out_of_core ();
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "CERTDATA",
&certdata, &certdatalen, MAXLEN_CERTDATA);
if (rc)
{
xfree (certid);
return rc;
}
/* Write the certificate to the card. */
rc = app_writecert (ctrl->card_ctx, ctrl, certid,
pin_cb, ctx, certdata, certdatalen);
xfree (certid);
xfree (certdata);
return rc;
}
static const char hlp_writekey[] =
"WRITEKEY [--force] <keyid> \n"
"\n"
"This command is used to store a secret key on a smartcard. The\n"
"allowed keyids depend on the currently selected smartcard\n"
"application. The actual keydata is requested using the inquiry\n"
"\"KEYDATA\" and need to be provided without any protection. With\n"
"--force set an existing key under this KEYID will get overwritten.\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"writekey function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyid;
int force = has_option (line, "--force");
unsigned char *keydata;
size_t keydatalen;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given");
keyid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyid = xtrystrdup (keyid);
if (!keyid)
return out_of_core ();
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
{
xfree (keyid);
return rc;
}
/* Write the key to the card. */
rc = app_writekey (ctrl->card_ctx, ctrl, keyid, force? 1:0,
pin_cb, ctx, keydata, keydatalen);
xfree (keyid);
xfree (keydata);
return rc;
}
static const char hlp_genkey[] =
"GENKEY [--force] [--timestamp=<isodate>] [--algo=ALGO] <keyref>\n"
"\n"
"Generate a key on-card identified by <keyref>, which is application\n"
"specific. Return values are also application specific. For OpenPGP\n"
"cards 3 status lines are returned:\n"
"\n"
" S KEY-FPR <hexstring>\n"
" S KEY-CREATED-AT <seconds_since_epoch>\n"
" S KEY-DATA [-|p|n] <hexdata>\n"
"\n"
" 'p' and 'n' are the names of the RSA parameters; '-' is used to\n"
" indicate that HEXDATA is the first chunk of a parameter given\n"
" by the next KEY-DATA. Only used by GnuPG version < 2.1.\n"
"\n"
"--force is required to overwrite an already existing key. The\n"
"KEY-CREATED-AT is required for further processing because it is\n"
"part of the hashed key material for the fingerprint.\n"
"\n"
"If --timestamp is given an OpenPGP key will be created using this\n"
"value. The value needs to be in ISO Format; e.g.\n"
"\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n"
"\n"
"The option --algo can be used to request creation using a specific\n"
"algorithm. The possible algorithms are card dependent.\n"
"\n"
"The public part of the key can also later be retrieved using the\n"
"READKEY command.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyref_buffer = NULL;
char *keyref;
int force;
const char *s;
char *opt_algo = NULL;
time_t timestamp;
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
timestamp = isotime2epoch (s+1);
if (timestamp < 1)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
}
else
timestamp = 0;
err = get_option_value (line, "--algo", &opt_algo);
if (err)
goto leave;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no key number given");
keyref = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((err = open_card (ctrl)))
goto leave;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyref = keyref_buffer = xtrystrdup (keyref);
if (!keyref)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = app_genkey (ctrl->card_ctx, ctrl, keyref, opt_algo,
force? APP_GENKEY_FLAG_FORCE : 0,
timestamp, pin_cb, ctx);
leave:
xfree (keyref_buffer);
xfree (opt_algo);
return err;
}
static const char hlp_random[] =
"RANDOM <nbytes>\n"
"\n"
"Get NBYTES of random from the card and send them back as data.\n"
"This usually involves EEPROM write on the card and thus excessive\n"
"use of this command may destroy the card.\n"
"\n"
"Note, that this function may be even be used on a locked card.";
static gpg_error_t
cmd_random (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
size_t nbytes;
unsigned char *buffer;
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER,
"number of requested bytes missing");
nbytes = strtoul (line, NULL, 0);
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
buffer = xtrymalloc (nbytes);
if (!buffer)
return out_of_core ();
rc = app_get_challenge (ctrl->card_ctx, ctrl, nbytes, buffer);
if (!rc)
{
rc = assuan_send_data (ctx, buffer, nbytes);
xfree (buffer);
return rc; /* that is already an assuan error code */
}
xfree (buffer);
return rc;
}
static const char hlp_passwd[] =
"PASSWD [--reset] [--nullpin] [--clear] <chvno>\n"
"\n"
"Change the PIN or, if --reset is given, reset the retry counter of\n"
"the card holder verification vector CHVNO. The option --nullpin is\n"
"used for TCOS cards to set the initial PIN. The option --clear clears\n"
"the security status associated with the PIN so that the PIN needs to\n"
"be presented again. The format of CHVNO depends on the card application.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *chvnostr;
unsigned int flags = 0;
if (has_option (line, "--reset"))
flags |= APP_CHANGE_FLAG_RESET;
if (has_option (line, "--nullpin"))
flags |= APP_CHANGE_FLAG_NULLPIN;
if (has_option (line, "--clear"))
flags |= APP_CHANGE_FLAG_CLEAR;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given");
chvnostr = line;
while (*line && !spacep (line))
line++;
*line = 0;
/* Do not allow other flags aside of --clear. */
if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR))
return set_error (GPG_ERR_UNSUPPORTED_OPERATION,
"--clear used with other options");
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
chvnostr = xtrystrdup (chvnostr);
if (!chvnostr)
return out_of_core ();
rc = app_change_pin (ctrl->card_ctx, ctrl, chvnostr, flags, pin_cb, ctx);
if (rc)
log_error ("command passwd failed: %s\n", gpg_strerror (rc));
xfree (chvnostr);
return rc;
}
static const char hlp_checkpin[] =
"CHECKPIN <idstr>\n"
"\n"
"Perform a VERIFY operation without doing anything else. This may\n"
"be used to initialize a the PIN cache earlier to long lasting\n"
"operations. Its use is highly application dependent.\n"
"\n"
"For OpenPGP:\n"
"\n"
" Perform a simple verify operation for CHV1 and CHV2, so that\n"
" further operations won't ask for CHV2 and it is possible to do a\n"
" cheap check on the PIN: If there is something wrong with the PIN\n"
" entry system, only the regular CHV will get blocked and not the\n"
" dangerous CHV3. IDSTR is the usual card's serial number in hex\n"
" notation; an optional fingerprint part will get ignored. There\n"
" is however a special mode if the IDSTR is suffixed with the\n"
" literal string \"[CHV3]\": In this case the Admin PIN is checked\n"
" if and only if the retry counter is still at 3.\n"
"\n"
"For Netkey:\n"
"\n"
" Any of the valid PIN Ids may be used. These are the strings:\n"
"\n"
" PW1.CH - Global password 1\n"
" PW2.CH - Global password 2\n"
" PW1.CH.SIG - SigG password 1\n"
" PW2.CH.SIG - SigG password 2\n"
"\n"
" For a definitive list, see the implementation in app-nks.c.\n"
" Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n"
" 3.0 they are technically alternative PINs used to mutally\n"
" unblock each other.";
static gpg_error_t
cmd_checkpin (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *idstr;
if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid. */
idstr = xtrystrdup (line);
if (!idstr)
return out_of_core ();
rc = app_check_pin (ctrl->card_ctx, ctrl, idstr, pin_cb, ctx);
xfree (idstr);
if (rc)
log_error ("app_check_pin failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_lock[] =
"LOCK [--wait]\n"
"\n"
"Grant exclusive card access to this session. Note that there is\n"
"no lock counter used and a second lock from the same session will\n"
"be ignored. A single unlock (or RESET) unlocks the session.\n"
"Return GPG_ERR_LOCKED if another session has locked the reader.\n"
"\n"
"If the option --wait is given the command will wait until a\n"
"lock has been released.";
static gpg_error_t
cmd_lock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
retry:
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
}
else
locked_session = ctrl->server_local;
#ifdef USE_NPTH
if (rc && has_option (line, "--wait"))
{
rc = 0;
npth_sleep (1); /* Better implement an event mechanism. However,
for card operations this should be
sufficient. */
/* FIXME: Need to check that the connection is still alive.
This can be done by issuing status messages. */
goto retry;
}
#endif /*USE_NPTH*/
if (rc)
log_error ("cmd_lock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_unlock[] =
"UNLOCK\n"
"\n"
"Release exclusive card access.";
static gpg_error_t
cmd_unlock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
(void)line;
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
else
locked_session = NULL;
}
else
rc = gpg_error (GPG_ERR_NOT_LOCKED);
if (rc)
log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" connections - Return number of active connections.\n"
" status - Return the status of the current reader (in the future,\n"
" may also return the status of all readers). The status\n"
" is a list of one-character flags. The following flags\n"
" are currently defined:\n"
" 'u' Usable card present.\n"
" 'r' Card removed. A reset is necessary.\n"
" These flags are exclusive.\n"
" reader_list - Return a list of detected card readers. Does\n"
" currently only work with the internal CCID driver.\n"
" deny_admin - Returns OK if admin commands are not allowed or\n"
" GPG_ERR_GENERAL if admin commands are allowed.\n"
" app_list - Return a list of supported applications. One\n"
" application per line, fields delimited by colons,\n"
" first field is the name.\n"
" card_list - Return a list of serial numbers of all inserted cards.\n"
" active_apps - Return a list of active apps on the current card.\n"
" all_active_apps\n"
" - Return a list of active apps on all inserted cards.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "socket_name"))
{
const char *s = scd_get_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "status"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char flag;
if (open_card (ctrl))
flag = 'r';
else
flag = 'u';
rc = assuan_send_data (ctx, &flag, 1);
}
else if (!strcmp (line, "reader_list"))
{
#ifdef HAVE_LIBUSB
char *s = ccid_get_reader_list ();
#else
char *s = NULL;
#endif
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
xfree (s);
}
else if (!strcmp (line, "deny_admin"))
rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0;
else if (!strcmp (line, "app_list"))
{
char *s = get_supported_applications ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = 0;
xfree (s);
}
else if (!strcmp (line, "card_list"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = app_send_card_list (ctrl);
}
else if (!strcmp (line, "active_apps"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->card_ctx)
rc = 0; /* No current card - no active apps. */
else
rc = app_send_active_apps (ctrl->card_ctx, ctrl);
}
else if (!strcmp (line, "all_active_apps"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = app_send_active_apps (NULL, ctrl);
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_restart[] =
"RESTART\n"
"\n"
"Restart the current connection; this is a kind of warm reset. It\n"
"deletes the context used by this connection but does not send a\n"
"RESET to the card. Thus the card itself won't get reset. \n"
"\n"
"This is used by gpg-agent to reuse a primary pipe connection and\n"
"may be used by clients to backup from a conflict in the serial\n"
"command; i.e. to select another application.";
static gpg_error_t
cmd_restart (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card = ctrl->card_ctx;
(void)line;
if (card)
{
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref (card);
}
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESTART\n");
}
return 0;
}
static const char hlp_disconnect[] =
"DISCONNECT\n"
"\n"
"Disconnect the card if the backend supports a disconnect operation.";
static gpg_error_t
cmd_disconnect (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (!ctrl->card_ctx)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
apdu_disconnect (ctrl->card_ctx->slot);
return 0;
}
static const char hlp_apdu[] =
"APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n"
"\n"
"Send an APDU to the current reader. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU. If HEXSTRING is not given no\n"
- "commands are set to the card but the command will implictly check\n"
+ "commands are set to the card but the command will implicitly check\n"
"whether the card is ready for use. \n"
"\n"
"Using the option \"--atr\" returns the ATR of the card as a status\n"
"message before any data like this:\n"
" S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n"
"\n"
"Using the option --more handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used\n"
"(currently 4096).";
static gpg_error_t
cmd_apdu (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card;
int rc;
unsigned char *apdu;
size_t apdulen;
int with_atr;
int handle_more;
const char *s;
size_t exlen;
if (has_option (line, "--dump-atr"))
with_atr = 2;
else
with_atr = has_option (line, "--atr");
handle_more = has_option (line, "--more");
if ((s=has_option_name (line, "--exlen")))
{
if (*s == '=')
exlen = strtoul (s+1, NULL, 0);
else
exlen = 4096;
}
else
exlen = 0;
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
card = ctrl->card_ctx;
if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
if (with_atr)
{
unsigned char *atr;
size_t atrlen;
char hexbuf[400];
atr = apdu_get_atr (card->slot, &atrlen);
if (!atr || atrlen > sizeof hexbuf - 2 )
{
rc = gpg_error (GPG_ERR_INV_CARD);
goto leave;
}
if (with_atr == 2)
{
char *string, *p, *pend;
string = atr_dump (atr, atrlen);
if (string)
{
for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1)
{
rc = assuan_send_data (ctx, p, pend - p + 1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (!rc && *p)
rc = assuan_send_data (ctx, p, strlen (p));
es_free (string);
if (rc)
goto leave;
}
}
else
{
bin2hex (atr, atrlen, hexbuf);
send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0);
}
xfree (atr);
}
apdu = hex_to_buffer (line, &apdulen);
if (!apdu)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (apdulen)
{
unsigned char *result = NULL;
size_t resultlen;
rc = apdu_send_direct (card->slot, exlen,
apdu, apdulen, handle_more,
NULL, &result, &resultlen);
if (rc)
log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc));
else
{
rc = assuan_send_data (ctx, result, resultlen);
xfree (result);
}
}
xfree (apdu);
leave:
return rc;
}
static const char hlp_killscd[] =
"KILLSCD\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killscd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_keyinfo[] =
"KEYINFO [--list[=auth|encr|sign]] [--data] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. Capability may limit the listing.\n"
"Unless --data is given, the\n"
"information is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> T <serialno> <idstr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
int cap;
int opt_data;
int action;
char *keygrip_str;
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card;
cap = 0;
keygrip_str = NULL;
if (has_option (line, "--list"))
cap = 0;
else if (has_option (line, "--list=sign"))
cap = 1;
else if (has_option (line, "--list=encr"))
cap = 2;
else if (has_option (line, "--list=auth"))
cap = 3;
else
keygrip_str = line;
opt_data = has_option (line, "--data");
line = skip_options (line);
if (opt_data)
action = KEYGRIP_ACTION_SEND_DATA;
else
action = KEYGRIP_ACTION_WRITE_STATUS;
card = app_do_with_keygrip (ctrl, action, keygrip_str, cap);
if (keygrip_str && !card)
return gpg_error (GPG_ERR_NOT_FOUND);
return 0;
}
/* Send a keyinfo string as used by the KEYGRIP_ACTION_SEND_DATA. If
* DATA is true the string is emitted as a data line, else as a status
* line. */
void
send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str,
const char *serialno, const char *idstr)
{
char *string;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
string = xtryasprintf ("%s T %s %s%s", keygrip_str,
serialno? serialno : "-",
idstr? idstr : "-",
data? "\n" : "");
if (!string)
return;
if (!data)
assuan_write_status (ctx, "KEYINFO", string);
else
assuan_send_data (ctx, string, strlen (string));
xfree (string);
return;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "SERIALNO"))
{
if (!strcmp (cmdopt, "all"))
return 1;
}
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SERIALNO", cmd_serialno, hlp_serialno },
{ "SWITCHCARD", cmd_switchcard,hlp_switchcard },
{ "SWITCHAPP", cmd_switchapp,hlp_switchapp },
{ "LEARN", cmd_learn, hlp_learn },
{ "READCERT", cmd_readcert, hlp_readcert },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "SETDATA", cmd_setdata, hlp_setdata },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKAUTH", cmd_pkauth, hlp_pkauth },
{ "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETATTR", cmd_getattr, hlp_getattr },
{ "SETATTR", cmd_setattr, hlp_setattr },
{ "WRITECERT", cmd_writecert,hlp_writecert },
{ "WRITEKEY", cmd_writekey, hlp_writekey },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "RANDOM", cmd_random, hlp_random },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "CHECKPIN", cmd_checkpin, hlp_checkpin },
{ "LOCK", cmd_lock, hlp_lock },
{ "UNLOCK", cmd_unlock, hlp_unlock },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "RESTART", cmd_restart, hlp_restart },
{ "DISCONNECT", cmd_disconnect,hlp_disconnect },
{ "APDU", cmd_apdu, hlp_apdu },
{ "KILLSCD", cmd_killscd, hlp_killscd },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
scd_command_handler (ctrl_t ctrl, int fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
scd_exit (2);
}
if (fd == -1)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD(fd),
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
assuan_set_pointer (ctx, ctrl);
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Cleanup. We don't send an explicit reset to the card. */
do_reset (ctrl, 0);
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
scd_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}
/* Send a line with status information via assuan and escape all given
buffers. The variable elements are pairs of (char *, size_t),
terminated with a (NULL, 0). */
void
send_status_info (ctrl_t ctrl, const char *keyword, ...)
{
va_list arg_ptr;
const unsigned char *value;
size_t valuelen;
char buf[950], *p;
size_t n;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
p = buf;
n = 0;
while ( (value = va_arg (arg_ptr, const unsigned char *))
&& n < DIM (buf)-2 )
{
valuelen = va_arg (arg_ptr, size_t);
if (!valuelen)
continue; /* empty buffer */
if (n)
{
*p++ = ' ';
n++;
}
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
{
if (*value == '+' || *value == '\"' || *value == '%'
|| *value < ' ')
{
sprintf (p, "%%%02X", *value);
p += 3;
n += 2;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
}
*p = 0;
assuan_write_status (ctx, keyword, buf);
va_end (arg_ptr);
}
/* Send a ready formatted status line via assuan. */
void
send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (strchr (args, '\n'))
log_error ("error: LF detected in status line - not sending\n");
else
assuan_write_status (ctx, keyword, args);
}
/* This status functions expects a printf style format string. No
* filtering of the data is done instead the printf formatted data is
* send using assuan_send_status. */
gpg_error_t
send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Set a gcrypt key for use with the pincache. The key is a random
* key unique for this process and is useless after this process has
* terminated. This way the cached PINs stored in the gpg-agent are
* bound to this specific process. The main purpose of this
* encryption is to hide the PIN in logs of the IPC. */
static gpg_error_t
set_key_for_pincache (gcry_cipher_hd_t hd)
{
static int initialized;
static unsigned char keybuf[16];
if (!initialized)
{
gcry_randomize (keybuf, sizeof keybuf, GCRY_STRONG_RANDOM);
initialized = 1;
}
return gcry_cipher_setkey (hd, keybuf, sizeof keybuf);
}
/* Store the PIN in the PIN cache. The key to identify the PIN
* consists of (SLOT,APPNAME,PINREF). If PIN is NULL the PIN stored
* under the given key is cleared. If APPNAME and PINREF are NULL the
* entire PIN cache for SLOT is cleared. If SLOT is -1 the entire PIN
* cache is cleared. We do no use an scdaemon internal cache but let
* gpg-agent cache it because it is better suited for this. */
void
pincache_put (ctrl_t ctrl, int slot, const char *appname, const char *pinref,
const char *pin, unsigned int pinlen)
{
gpg_error_t err;
assuan_context_t ctx;
char line[950];
gcry_cipher_hd_t cipherhd = NULL;
char *pinbuf = NULL;
unsigned char *wrappedkey = NULL;
size_t pinbuflen, wrappedkeylen;
if (!ctrl)
{
/* No CTRL object provided. We could pick an arbitrary
* connection and send the status to that one. However, such a
* connection is inlikley to wait for a respinse from use and
* thus it would at best be read as a response to the next
* command send to us. That is not good because it may clog up
* our connection. Thus we better don't do that. A better will
* be to queue this up and let the agent poll for general status
* messages. */
/* struct server_local_s *sl; */
/* for (sl=session_list; sl; sl = sl->next_session) */
/* if (sl->ctrl_backlink && sl->ctrl_backlink->server_local */
/* && sl->ctrl_backlink->server_local->assuan_ctx) */
/* { */
/* ctrl = sl->ctrl_backlink; */
/* break; */
/* } */
}
if (!ctrl || !ctrl->server_local || !(ctx=ctrl->server_local->assuan_ctx))
return;
if (pin && !pinlen)
return; /* Ignore an empty PIN. */
snprintf (line, sizeof line, "%d/%s/%s ",
slot, appname? appname:"", pinref? pinref:"");
/* Without an APPNAME etc or without a PIN we clear the cache and
* thus there is no need to send the pin - even if the caller
- * accidentially passed a pin. */
+ * accidentally passed a pin. */
if (pin && slot != -1 && appname && pinref)
{
/* FIXME: Replace this by OCB mode and use the cache key as
* additional data. */
/* Pad with zeroes (AESWRAP requires multiples of 64 bit but
* at least 128 bit data). */
pinbuflen = pinlen + 8 - (pinlen % 8);
if (pinbuflen < 16)
pinbuflen = 16;
pinbuf = xtrycalloc_secure (1, pinbuflen);
if (!pinbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (pinbuf, pin, pinlen);
pinlen = pinbuflen;
pin = pinbuf;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = set_key_for_pincache (cipherhd);
if (err)
goto leave;
wrappedkeylen = pinlen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
pin, pinlen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
if (strlen (line) + 2*wrappedkeylen + 1 >= sizeof line)
{
log_error ("%s: PIN or pinref string too long - ignored", __func__);
goto leave;
}
bin2hex (wrappedkey, wrappedkeylen, line + strlen (line));
}
send_status_direct (ctrl, "PINCACHE_PUT", line);
leave:
xfree (pinbuf);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
if (err)
log_error ("%s: error caching PIN: %s\n", __func__, gpg_strerror (err));
}
/* Ask the agent for a cached PIN for the tuple (SLOT,APPNAME,PINREF).
* Returns on success and stores the PIN at R_PIN; the caller needs to
* wipe(!) and then free that value. On error NULL is stored at
* R_PIN and an error code returned. Common error codes are:
* GPG_ERR_NOT_SUPPORTED - Client does not support the PIN cache
* GPG_ERR_NO_DATA - No PIN cached for the given key tuple
*/
gpg_error_t
pincache_get (ctrl_t ctrl, int slot, const char *appname, const char *pinref,
char **r_pin)
{
gpg_error_t err;
assuan_context_t ctx;
char command[512];
unsigned char *value = NULL;
size_t valuelen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
if (slot == -1 || !appname || !pinref || !r_pin)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
{
err = gpg_error (GPG_ERR_USE_CONDITIONS);
log_error ("%s: called w/o assuan context\n", __func__);
goto leave;
}
snprintf (command, sizeof command, "PINCACHE_GET %d/%s/%s",
slot, appname? appname:"", pinref? pinref:"");
/* Limit the inquire to something reasonable. The 32 extra bytes
* are a guessed size for padding etc. */
err = assuan_inquire (ctx, command, &wrappedkey, &wrappedkeylen,
2*MAXLEN_PIN+32);
if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
log_info ("caller does not feature a PIN cache");
goto leave;
}
if (err)
{
log_error ("%s: sending PINCACHE_GET to caller failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (!wrappedkey || !wrappedkeylen)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Convert to hex to binary and store it in (wrappedkey, wrappedkeylen). */
if (!hex2str (wrappedkey, wrappedkey, wrappedkeylen, &wrappedkeylen))
{
err = gpg_error_from_syserror ();
log_error ("%s: caller returned invalid hex string: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (!wrappedkey || wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH); /* too short cryptogram */
goto leave;
}
valuelen = wrappedkeylen - 8;
value = xtrymalloc_secure (valuelen);
if (!value)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = set_key_for_pincache (cipherhd);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, value, valuelen,
wrappedkey, wrappedkeylen);
if (err)
{
log_error ("%s: cached value could not be decrypted: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
*r_pin = value;
value = NULL;
leave:
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (value);
return err;
}
void
popup_prompt (void *opaque, int on)
{
ctrl_t ctrl = opaque;
if (ctrl)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (ctx)
{
const char *cmd;
gpg_error_t err;
unsigned char *value;
size_t valuelen;
if (on)
cmd = "POPUPPINPADPROMPT --ack";
else
cmd = "DISMISSPINPADPROMPT";
err = assuan_inquire (ctx, cmd, &value, &valuelen, 100);
if (!err)
xfree (value);
}
}
}
/* Helper to send the clients a status change notification. Note that
* this function assumes that APP is already locked. */
void
send_client_notifications (card_t card, int removal)
{
struct {
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
} killed[50];
int killidx = 0;
int kidx;
struct server_local_s *sl;
for (sl=session_list; sl; sl = sl->next_session)
if (sl->ctrl_backlink && sl->ctrl_backlink->card_ctx == card)
{
pid_t pid;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
int signo;
#endif
if (removal)
{
sl->ctrl_backlink->card_ctx = NULL;
sl->ctrl_backlink->current_apptype = APPTYPE_NONE;
sl->card_removed = 1;
card_unref_locked (card);
}
if (!sl->event_signal || !sl->assuan_ctx)
continue;
pid = assuan_get_pid (sl->assuan_ctx);
#ifdef HAVE_W32_SYSTEM
handle = sl->event_signal;
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].handle == handle)
break;
if (kidx < killidx)
log_info ("event %p (%p) already triggered for client %d\n",
sl->event_signal, handle, (int)pid);
else
{
log_info ("triggering event %p (%p) for client %d\n",
sl->event_signal, handle, (int)pid);
if (!SetEvent (handle))
log_error ("SetEvent(%p) failed: %s\n",
sl->event_signal, w32_strerror (-1));
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].handle = handle;
killidx++;
}
}
#else /*!HAVE_W32_SYSTEM*/
signo = sl->event_signal;
if (pid != (pid_t)(-1) && pid && signo > 0)
{
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].signo == signo)
break;
if (kidx < killidx)
log_info ("signal %d already sent to client %d\n",
signo, (int)pid);
else
{
log_info ("sending signal %d to client %d\n",
signo, (int)pid);
kill (pid, signo);
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].signo = signo;
killidx++;
}
}
}
#endif /*!HAVE_W32_SYSTEM*/
}
}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 942944f38..e64bb004e 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -1,1463 +1,1463 @@
/* scdaemon.c - The GnuPG Smartcard Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "scdaemon.h"
#include <ksba.h>
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "iso7816.h"
#include "apdu.h"
#include "ccid-driver.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugCCIDDriver,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oBatch,
oReaderPort,
oCardTimeout,
octapiDriver,
opcscDriver,
oDisableCCID,
oDisableOpenSC,
oDisablePinpad,
oAllowAdmin,
oDenyAdmin,
oDisableApplication,
oApplicationPriority,
oEnablePinpadVarlen,
oListenBacklog
};
static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oReaderPort, "reader-port",
N_("|N|connect to reader at port N")),
ARGPARSE_s_s (octapiDriver, "ctapi-driver",
N_("|NAME|use NAME as ct-API driver")),
ARGPARSE_s_s (opcscDriver, "pcsc-driver",
N_("|NAME|use NAME as PC/SC driver")),
ARGPARSE_s_n (oDisableCCID, "disable-ccid",
#ifdef HAVE_LIBUSB
N_("do not use the internal CCID driver")
#else
"@"
#endif
/* end --disable-ccid */),
ARGPARSE_s_u (oCardTimeout, "card-timeout",
N_("|N|disconnect the card after N seconds of inactivity")),
ARGPARSE_s_n (oDisablePinpad, "disable-pinpad",
N_("do not use a reader's pinpad")),
ARGPARSE_ignore (300, "disable-keypad"),
ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"),
ARGPARSE_s_n (oDenyAdmin, "deny-admin",
N_("deny the use of admin card commands")),
ARGPARSE_s_s (oDisableApplication, "disable-application", "@"),
ARGPARSE_s_s (oApplicationPriority, "application-priority",
N_("|LIST|Change the application priority to LIST")),
ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen",
N_("use variable length input for pinpad")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CARD_IO_VALUE, "cardio" },
{ DBG_READER_VALUE , "reader" },
{ DBG_APP_VALUE , "app" },
{ 0, NULL }
};
/* The card driver we use by default for PC/SC. */
#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__)
#define DEFAULT_PCSC_DRIVER "winscard.dll"
#elif defined(__APPLE__)
#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC"
#elif defined(__GLIBC__)
#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1"
#else
#define DEFAULT_PCSC_DRIVER "libpcsclite.so"
#endif
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (int listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 11: p = "@SCDAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 1:
case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
scd_exit(2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
static void
setup_signal_mask (void)
{
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
int orig_argc;
char **orig_argv;
FILE *configfp = NULL;
char *configname = NULL;
const char *shell;
unsigned int configlineno;
int parse_debug = 0;
const char *debug_level = NULL;
int default_config =1;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
const char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
const char *application_priority = NULL;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.allow_admin = 1;
opt.pcsc_driver = DEFAULT_PCSC_DRIVER;
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
while (arg_parse( &pargs, opts))
{
if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
parse_debug++;
else if (pargs.r_opt == oOptions)
{ /* yes there is one, so we do not try the default one, but
read the option file when it is encountered at the
commandline */
default_config = 0;
}
else if (pargs.r_opt == oNoOptions)
default_config = 0; /* --no-options */
else if (pargs.r_opt == oHomedir)
gnupg_set_homedir (pargs.r.ret_str);
}
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
if (default_config)
configname = make_filename (gnupg_homedir (), SCDAEMON_NAME EXTSEP_S "conf",
NULL );
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* do not remove the args */
next_pass:
if (configname)
{
configlineno = 0;
configfp = fopen (configname, "r");
if (!configfp)
{
if (default_config)
{
if( parse_debug )
log_info (_("Note: no default option file '%s'\n"),
configname );
}
else
{
log_error (_("option file '%s': %s\n"),
configname, strerror(errno) );
exit(2);
}
xfree (configname);
configname = NULL;
}
if (parse_debug && configname )
log_info (_("reading options from '%s'\n"), configname );
default_config = 0;
}
while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
{
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugCCIDDriver:
#ifdef HAVE_LIBUSB
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
#endif /*HAVE_LIBUSB*/
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oOptions:
/* config files may not be nested (silently ignore them) */
if (!configfp)
{
xfree(configname);
configname = xstrdup(pargs.r.ret_str);
goto next_pass;
}
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oDisableOpenSC: break;
case oDisablePinpad: opt.disable_pinpad = 1; break;
case oAllowAdmin: /* Dummy because allow is now the default. */
break;
case oDenyAdmin: opt.allow_admin = 0; break;
case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break;
case oDisableApplication:
add_to_strlist (&opt.disabled_applications, pargs.r.ret_str);
break;
case oApplicationPriority:
application_priority = pargs.r.ret_str;
break;
case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
if (configfp)
{
fclose( configfp );
configfp = NULL;
/* Keep a copy of the config name for use by --gpgconf-list. */
config_filename = configname;
configname = NULL;
goto next_pass;
}
xfree (configname);
configname = NULL;
if (log_get_errorcount(0))
exit(2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
strusage(11), strusage(13), strusage(14) );
es_fprintf (es_stderr, "%s\n", strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (initialize_module_command ())
{
log_error ("initialization failed\n");
cleanup ();
exit (1);
}
if (gpgconf_list == 2)
scd_exit (0);
if (gpgconf_list)
{
/* List options and default values in the GPG Conf format. */
char *filename = NULL;
char *filename_esc;
if (config_filename)
filename = xstrdup (config_filename);
else
filename = make_filename (gnupg_homedir (),
SCDAEMON_NAME EXTSEP_S "conf", NULL);
filename_esc = percent_escape (filename, NULL);
es_printf ("%s-%s.conf:%lu:\"%s\n",
GPGCONF_NAME, SCDAEMON_NAME,
GC_OPT_FLAG_DEFAULT, filename_esc);
xfree (filename_esc);
xfree (filename);
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("pcsc-driver:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER );
#ifdef HAVE_LIBUSB
es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE );
#endif
es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0);
es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE );
es_printf ("application-priority:%lu:\n", GC_OPT_FLAG_NONE );
scd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (application_priority)
app_update_priority_list (application_priority);
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
int fd = -1;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT(create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
scd_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
scd_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
scd_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != -1)
close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
int fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = FD2INT (create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export SCDAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
close (fd);
}
return 0;
}
void
scd_exit (int rc)
{
apdu_prepare_exit ();
#if 0
#warning no update_random_seed_file
update_random_seed_file();
#endif
#if 0
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
static void
scd_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
scd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
scd_get_socket_name ()
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration and resetting cards\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
app_dump_state ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", strusage(11), strusage(13) );
cleanup ();
scd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", strusage(11), strusage(13));
cleanup ();
scd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
- Returns: Pointer to an allcoated string with the absolute name of
+ Returns: Pointer to an allocated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
scd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
scd_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
scd_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT(ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
scd_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD2INT(ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (scd_command_handler (ctrl, FD2INT(ctrl->thread_startup.fd))
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD2INT (ctrl->thread_startup.fd));
scd_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
scd_kick_the_loop ();
return NULL;
}
void
scd_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (int listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
int fd;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init(&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != -1)
{
FD_SET (listen_fd, &fdset);
nfd = listen_fd;
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = -1;
}
periodical_check = scd_update_reader_status_file ();
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#else
(void)max_fd;
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset))
{
ctrl_t ctrl;
plen = sizeof paddr;
fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen);
if (fd == -1)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", fd);
ctrl->thread_startup.fd = INT2FD (fd);
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), strusage(11), strusage(13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/sm/certchain.c b/sm/certchain.c
index 6b970de9a..77e91f003 100644
--- a/sm/certchain.c
+++ b/sm/certchain.c
@@ -1,2225 +1,2225 @@
/* certchain.c - certificate chain validation
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2006, 2007, 2008, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdarg.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/i18n.h"
#include "../common/tlv.h"
/* Object to keep track of certain root certificates. */
struct marktrusted_info_s
{
struct marktrusted_info_s *next;
unsigned char fpr[20];
};
static struct marktrusted_info_s *marktrusted_info;
/* While running the validation function we want to keep track of the
certificates in the chain. This type is used for that. */
struct chain_item_s
{
struct chain_item_s *next;
ksba_cert_t cert; /* The certificate. */
int is_root; /* The certificate is the root certificate. */
};
typedef struct chain_item_s *chain_item_t;
static int is_root_cert (ksba_cert_t cert,
const char *issuerdn, const char *subjectdn);
static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen);
/* This function returns true if we already asked during this session
whether the root certificate CERT shall be marked as trusted. */
static int
already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
/* No context switches in the loop! */
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return 1;
return 0;
}
/* Flag certificate CERT as already asked whether it shall be marked
as trusted. */
static void
set_already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return; /* Already marked. */
r = xtrycalloc (1, sizeof *r);
if (!r)
return;
memcpy (r->fpr, fpr, 20);
r->next = marktrusted_info;
marktrusted_info = r;
}
/* If LISTMODE is true, print FORMAT using LISTMODE to FP. If
LISTMODE is false, use the string to print an log_info or, if
IS_ERROR is true, and log_error. */
static void
do_list (int is_error, int listmode, estream_t fp, const char *format, ...)
{
va_list arg_ptr;
va_start (arg_ptr, format) ;
if (listmode)
{
if (fp)
{
es_fputs (" [", fp);
es_vfprintf (fp, format, arg_ptr);
es_fputs ("]\n", fp);
}
}
else
{
log_logv (is_error? GPGRT_LOGLVL_ERROR: GPGRT_LOGLVL_INFO,
format, arg_ptr);
log_printf ("\n");
}
va_end (arg_ptr);
}
/* Return 0 if A and B are equal. */
static int
compare_certs (ksba_cert_t a, ksba_cert_t b)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (a, &len_a);
if (!img_a)
return 1;
img_b = ksba_cert_get_image (b, &len_b);
if (!img_b)
return 1;
return !(len_a == len_b && !memcmp (img_a, img_b, len_a));
}
/* Return true if CERT has the validityModel extensions and defines
the use of the chain model. */
static int
has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp)
{
gpg_error_t err;
int idx, yes;
const char *oid;
size_t off, derlen, objlen, hdrlen;
const unsigned char *der;
int class, tag, constructed, ndef;
char *oidbuf;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &derlen));idx++)
if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") )
break;
if (err)
return 0; /* Not found. */
der = ksba_cert_get_image (cert, NULL);
if (!der)
{
err = gpg_error (GPG_ERR_INV_OBJ); /* Oops */
goto leave;
}
der += off;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
derlen = objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OBJECT_ID))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
oidbuf = ksba_oid_to_str (der, objlen);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
do_list (0, listmode, listfp,
_("validation model requested by certificate: %s"),
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") :
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") :
/* */ oidbuf);
yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1");
ksba_free (oidbuf);
return yes;
leave:
log_error ("error parsing validityModel: %s\n", gpg_strerror (err));
return 0;
}
static int
unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp)
{
static const char *known[] = {
"2.5.29.15", /* keyUsage */
"2.5.29.17", /* subjectAltName
Japanese DoCoMo certs mark them as critical. PKIX
only requires them as critical if subjectName is
empty. I don't know whether our code gracefully
handles such empry subjectNames but that is
another story. */
"2.5.29.19", /* basic Constraints */
"2.5.29.32", /* certificatePolicies */
"2.5.29.37", /* extendedKeyUsage - handled by certlist.c */
"1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */
NULL
};
int rc = 0, i, idx, crit;
const char *oid;
gpg_error_t err;
int unsupported;
strlist_t sl;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &crit, NULL, NULL));idx++)
{
if (!crit)
continue;
for (i=0; known[i] && strcmp (known[i],oid); i++)
;
unsupported = !known[i];
/* If this critical extension is not supported. Check the list
of to be ignored extensions to see whether we claim that it
is supported. */
if (unsupported && opt.ignored_cert_extensions)
{
for (sl=opt.ignored_cert_extensions;
sl && strcmp (sl->d, oid); sl = sl->next)
;
if (sl)
unsupported = 0;
}
if (unsupported)
{
do_list (1, listmode, fp,
_("critical certificate extension %s is not supported"),
oid);
rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
}
}
/* We ignore the error codes EOF as well as no-value. The later will
occur for certificates with no extensions at all. */
if (err
&& gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
rc = err;
return rc;
}
/* Check whether CERT is an allowed certificate. This requires that
CERT matches all requirements for such a CA, i.e. the
BasicConstraints extension. The function returns 0 on success and
the allowed length of the chain at CHAINLEN. */
static int
allowed_ca (ctrl_t ctrl,
ksba_cert_t cert, int *chainlen, int listmode, estream_t fp)
{
gpg_error_t err;
int flag;
err = ksba_cert_is_ca (cert, &flag, chainlen);
if (err)
return err;
if (!flag)
{
if (get_regtp_ca_info (ctrl, cert, chainlen))
{
/* Note that dirmngr takes a different way to cope with such
certs. */
return 0; /* RegTP issued certificate. */
}
do_list (1, listmode, fp,_("issuer certificate is not marked as a CA"));
return gpg_error (GPG_ERR_BAD_CA_CERT);
}
return 0;
}
static int
check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist)
{
gpg_error_t err;
char *policies;
FILE *fp;
int any_critical;
err = ksba_cert_get_cert_policies (cert, &policies);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 0; /* No policy given. */
if (err)
return err;
/* STRING is a line delimited list of certificate policies as stored
in the certificate. The line itself is colon delimited where the
first field is the OID of the policy and the second field either
N or C for normal or critical extension */
if (opt.verbose > 1 && !listmode)
log_info ("certificate's policy list: %s\n", policies);
/* The check is very minimal but won't give false positives */
any_critical = !!strstr (policies, ":C");
if (!opt.policy_file)
{
xfree (policies);
if (any_critical)
{
do_list (1, listmode, fplist,
_("critical marked policy without configured policies"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
return 0;
}
fp = fopen (opt.policy_file, "r");
if (!fp)
{
if (opt.verbose || errno != ENOENT)
log_info (_("failed to open '%s': %s\n"),
opt.policy_file, strerror (errno));
xfree (policies);
/* With no critical policies this is only a warning */
if (!any_critical)
{
if (!opt.quiet)
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
for (;;)
{
int c;
char *p, line[256];
char *haystack, *allowed;
/* read line */
do
{
if (!fgets (line, DIM(line)-1, fp) )
{
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
xfree (policies);
if (feof (fp))
{
fclose (fp);
/* With no critical policies this is only a warning */
if (!any_critical)
{
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
fclose (fp);
return tmperr;
}
if (!*line || line[strlen(line)-1] != '\n')
{
/* eat until end of line */
while ( (c=getc (fp)) != EOF && c != '\n')
;
fclose (fp);
xfree (policies);
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
/* Parse line. Note that the line has always a LF and spacep
does not consider a LF a space. Thus strpbrk will always
succeed. */
for (allowed=line; spacep (allowed); allowed++)
;
p = strpbrk (allowed, " :\n");
if (!*p || p == allowed)
{
fclose (fp);
xfree (policies);
return gpg_error (GPG_ERR_CONFIGURATION);
}
*p = 0; /* strip the rest of the line */
/* See whether we find ALLOWED (which is an OID) in POLICIES */
for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1)
{
if ( !(p == policies || p[-1] == '\n') )
continue; /* Does not match the begin of a line. */
if (p[strlen (allowed)] != ':')
continue; /* The length does not match. */
/* Yep - it does match so return okay. */
fclose (fp);
xfree (policies);
return 0;
}
}
}
/* Helper function for find_up. This resets the key handle and search
for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns
0 on success or -1 when not found. */
static int
find_up_search_by_keyid (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
ksba_cert_t cert = NULL;
ksba_sexp_t subj = NULL;
ksba_isotime_t not_before, not_after, last_not_before, ne_last_not_before;
ksba_cert_t found_cert = NULL;
ksba_cert_t ne_found_cert = NULL;
keydb_search_reset (kh);
while (!(rc = keydb_search_subject (ctrl, kh, issuer)))
{
ksba_cert_release (cert); cert = NULL;
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = -1;
goto leave;
}
xfree (subj);
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
{
if (!cmp_simple_canon_sexp (keyid, subj))
{
/* Found matching cert. */
rc = ksba_cert_get_validity (cert, 0, not_before);
if (!rc)
rc = ksba_cert_get_validity (cert, 1, not_after);
if (rc)
{
log_error ("keydb_get_validity() failed: rc=%d\n", rc);
rc = -1;
goto leave;
}
if (!found_cert
|| strcmp (last_not_before, not_before) < 0)
{
/* This certificate is the first one found or newer
* than the previous one. This copes with
* re-issuing CA certificates while keeping the same
* key information. */
gnupg_copy_time (last_not_before, not_before);
ksba_cert_release (found_cert);
ksba_cert_ref ((found_cert = cert));
keydb_push_found_state (kh);
}
if (*not_after && strcmp (ctrl->current_time, not_after) > 0 )
; /* CERT has expired - don't consider it. */
else if (!ne_found_cert
|| strcmp (ne_last_not_before, not_before) < 0)
{
/* This certificate is the first non-expired one
* found or newer than the previous non-expired one. */
gnupg_copy_time (ne_last_not_before, not_before);
ksba_cert_release (ne_found_cert);
ksba_cert_ref ((ne_found_cert = cert));
}
}
}
}
if (!found_cert)
goto leave;
/* Take the last saved one. Note that push/pop_found_state are
* misnomers because there is no stack of states. Renaming them to
* save/restore_found_state would be better. */
keydb_pop_found_state (kh);
rc = 0; /* Ignore EOF or other error after the first cert. */
/* We need to consider some corner cases. It is possible that we
* have a long term certificate (e.g. valid from 2008 to 2033) as
* well as a re-issued (i.e. using the same key material) short term
* certificate (say from 2016 to 2019). Using the short term
* certificate is the proper solution. But we need to take care if
* there is no re-issued new short term certificate (e.g. from 2020
* to 2023) available. In that case it is better to use the long
* term certificate which is still valid. The code may run into
* minor problems in the case of the chain validation mode. Given
* that this corner case is due to non-diligent PKI management we
* ignore this problem. */
/* The most common case is that the found certificate is not expired
* and thus identical to the one found from the list of non-expired
* certs. We can stop here. */
if (found_cert == ne_found_cert)
goto leave;
/* If we do not have a non expired certificate the actual cert is
* expired and we can also stop here. */
if (!ne_found_cert)
goto leave;
/* Now we need to see whether the found certificate is expired and
* only in this case we return the certificate found in the list of
* non-expired certs. */
rc = ksba_cert_get_validity (found_cert, 1, not_after);
if (rc)
{
log_error ("keydb_get_validity() failed: rc=%d\n", rc);
rc = -1;
goto leave;
}
if (*not_after && strcmp (ctrl->current_time, not_after) > 0 )
{ /* CERT has expired. Use the NE_FOUND_CERT. Because we have no
* found state for this we need to search for it again. */
unsigned char fpr[20];
gpgsm_get_fingerprint (ne_found_cert, GCRY_MD_SHA1, fpr, NULL);
keydb_search_reset (kh);
rc = keydb_search_fpr (ctrl, kh, fpr);
if (rc)
{
log_error ("keydb_search_fpr() failed: rc=%d\n", rc);
rc = -1;
goto leave;
}
- /* Ready. The NE_FOUND_CERT is availabale via keydb_get_cert. */
+ /* Ready. The NE_FOUND_CERT is available via keydb_get_cert. */
}
leave:
ksba_cert_release (found_cert);
ksba_cert_release (ne_found_cert);
ksba_cert_release (cert);
xfree (subj);
return rc? -1:0;
}
struct find_up_store_certs_s
{
ctrl_t ctrl;
int count;
};
static void
find_up_store_certs_cb (void *cb_value, ksba_cert_t cert)
{
struct find_up_store_certs_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing issuer certificate as ephemeral\n");
parm->count++;
}
/* Helper for find_up(). Locate the certificate for ISSUER using an
external lookup. KH is the keydb context we are currently using.
On success 0 is returned and the certificate may be retrieved from
the keydb using keydb_get_cert(). KEYID is the keyIdentifier from
the AKI or NULL. */
static int
find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
const char *s;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer at external location\n"));
/* The Dirmngr process is confused about unknown attributes. As a
quick and ugly hack we locate the CN and use the issuer string
starting at this attribite. Fixme: we should have far better
parsing for external lookups in the Dirmngr. */
s = strstr (issuer, "CN=");
if (!s || s == issuer || s[-1] != ',')
s = issuer;
pattern = xtrymalloc (strlen (s)+2);
if (!pattern)
return gpg_error_from_syserror ();
strcpy (stpcpy (pattern, "/"), s);
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, 0, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of issuers matching: %d\n"),
find_up_store_certs_parm.count);
if (rc)
{
log_error ("external key lookup failed: %s\n", gpg_strerror (rc));
rc = -1;
}
else if (!find_up_store_certs_parm.count)
rc = -1;
else
{
int old;
/* The issuers are currently stored in the ephemeral key DB, so
we temporary switch to ephemeral mode. */
old = keydb_set_ephemeral (kh, 1);
if (keyid)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
return rc;
}
/* Helper for find_up(). Ask the dirmngr for the certificate for
ISSUER with optional SERIALNO. KH is the keydb context we are
currently using. With SUBJECT_MODE set, ISSUER is searched as the
subject. On success 0 is returned and the certificate is available
in the ephemeral DB. */
static int
find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_sexp_t serialno, const char *issuer, int subject_mode)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
(void)kh;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer from the Dirmngr cache\n"));
if (subject_mode)
{
pattern = xtrymalloc (strlen (issuer)+2);
if (pattern)
strcpy (stpcpy (pattern, "/"), issuer);
}
else if (serialno)
pattern = gpgsm_format_sn_issuer (serialno, issuer);
else
{
pattern = xtrymalloc (strlen (issuer)+3);
if (pattern)
strcpy (stpcpy (pattern, "#/"), issuer);
}
if (!pattern)
return gpg_error_from_syserror ();
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, 1, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of matching certificates: %d\n"),
find_up_store_certs_parm.count);
if (rc && !opt.quiet)
log_info (_("dirmngr cache-only key lookup failed: %s\n"),
gpg_strerror (rc));
return (!rc && find_up_store_certs_parm.count)? 0 : -1;
}
/* Locate issuing certificate for CERT. ISSUER is the name of the
issuer used as a fallback if the other methods don't work. If
FIND_NEXT is true, the function shall return the next possible
issuer. The certificate itself is not directly returned but a
keydb_get_cert on the keydb context KH will return it. Returns 0
on success, -1 if not found or an error code. */
static int
find_up (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_cert_t cert, const char *issuer, int find_next)
{
ksba_name_t authid;
ksba_sexp_t authidno;
ksba_sexp_t keyid;
int rc = -1;
if (DBG_X509)
log_debug ("looking for parent certificate\n");
if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno))
{
const char *s = ksba_name_enum (authid, 0);
if (s && *authidno)
{
rc = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (rc)
keydb_search_reset (kh);
if (!rc && DBG_X509)
log_debug (" found via authid and sn+issuer\n");
/* In case of an error, try to get the certificate from the
dirmngr. That is done by trying to put that certificate
into the ephemeral DB and let the code below do the
actual retrieve. Thus there is no error checking.
Skipped in find_next mode as usual. */
if (rc == -1 && !find_next)
find_up_dirmngr (ctrl, kh, authidno, s, 0);
/* In case of an error try the ephemeral DB. We can't do
that in find_next mode because we can't keep the search
state then. */
if (rc == -1 && !find_next)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
{
rc = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (rc)
keydb_search_reset (kh);
if (!rc && DBG_X509)
log_debug (" found via authid and sn+issuer (ephem)\n");
}
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
}
if (rc == -1 && keyid && !find_next)
{
/* Not found by AKI.issuer_sn. Lets try the AKI.ki
instead. Loop over all certificates with that issuer as
subject and stop for the one with a matching
subjectKeyIdentifier. */
/* Fixme: Should we also search in the dirmngr? */
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and keyid\n");
if (rc)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and keyid (ephem)\n");
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
}
/* If we still didn't found it, try to find it via the subject
from the dirmngr-cache. */
if (rc == -1 && !find_next)
{
if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1))
{
int old = keydb_set_ephemeral (kh, 1);
if (keyid)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
if (rc)
rc = -1; /* Need to make sure to have this error code. */
if (!rc && DBG_X509)
log_debug (" found via authid and issuer from dirmngr cache\n");
}
/* If we still didn't found it, try an external lookup. */
if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
{
rc = find_up_external (ctrl, kh, issuer, keyid);
if (!rc && DBG_X509)
log_debug (" found via authid and external lookup\n");
}
/* Print a note so that the user does not feel too helpless when
an issuer certificate was found and gpgsm prints BAD
signature because it is not the correct one. */
if (rc == -1 && opt.quiet)
;
else if (rc == -1)
{
log_info ("%sissuer certificate ", find_next?"next ":"");
if (keyid)
{
log_printf ("{");
gpgsm_dump_serial (keyid);
log_printf ("} ");
}
if (authidno)
{
log_printf ("(#");
gpgsm_dump_serial (authidno);
log_printf ("/");
gpgsm_dump_string (s);
log_printf (") ");
}
log_printf ("not found using authorityKeyIdentifier\n");
}
else if (rc)
log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc);
xfree (keyid);
ksba_name_release (authid);
xfree (authidno);
}
if (rc) /* Not found via authorithyKeyIdentifier, try regular issuer name. */
rc = keydb_search_subject (ctrl, kh, issuer);
if (rc == -1 && !find_next)
{
int old;
/* Also try to get it from the Dirmngr cache. The function
merely puts it into the ephemeral database. */
find_up_dirmngr (ctrl, kh, NULL, issuer, 0);
/* Not found, let us see whether we have one in the ephemeral key DB. */
old = keydb_set_ephemeral (kh, 1);
if (!old)
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
if (!rc && DBG_X509)
log_debug (" found via issuer\n");
}
/* Still not found. If enabled, try an external lookup. */
if (rc == -1 && opt.auto_issuer_key_retrieve && !find_next)
{
rc = find_up_external (ctrl, kh, issuer, NULL);
if (!rc && DBG_X509)
log_debug (" found via issuer and external lookup\n");
}
return rc;
}
/* Return the next certificate up in the chain starting at START.
Returns -1 when there are no more certificates. */
int
gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next)
{
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = keydb_new ();
*r_next = NULL;
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (start, 0);
subject = ksba_cert_get_subject (start, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (!subject)
{
log_error ("no subject found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (start, issuer, subject))
{
rc = -1; /* we are at the root */
goto leave;
}
rc = find_up (ctrl, kh, start, issuer, 0);
if (rc)
{
/* It is quite common not to have a certificate, so better don't
print an error here. */
if (rc != -1 && opt.verbose > 1)
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
rc = keydb_get_cert (kh, r_next);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
return rc;
}
/* Helper for gpgsm_is_root_cert. This one is used if the subject and
issuer DNs are already known. */
static int
is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
{
gpg_error_t err;
int result = 0;
ksba_sexp_t serialno;
ksba_sexp_t ak_keyid;
ksba_name_t ak_name;
ksba_sexp_t ak_sn;
const char *ak_name_str;
ksba_sexp_t subj_keyid = NULL;
if (!issuerdn || !subjectdn)
return 0; /* No. */
if (strcmp (issuerdn, subjectdn))
return 0; /* No. */
err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 1; /* Yes. Without a authorityKeyIdentifier this needs
to be the Root certificate (our trust anchor). */
log_error ("error getting authorityKeyIdentifier: %s\n",
gpg_strerror (err));
return 0; /* Well, it is broken anyway. Return No. */
}
serialno = ksba_cert_get_serial (cert);
if (!serialno)
{
log_error ("error getting serialno: %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether the auth name's matches the issuer name+sn. If
that is the case this is a root certificate. */
ak_name_str = ksba_name_enum (ak_name, 0);
if (ak_name_str
&& !strcmp (ak_name_str, issuerdn)
&& !cmp_simple_canon_sexp (ak_sn, serialno))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
/* Similar for the ak_keyid. */
if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
&& !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
leave:
ksba_free (subj_keyid);
ksba_free (ak_keyid);
ksba_name_release (ak_name);
ksba_free (ak_sn);
ksba_free (serialno);
return result;
}
/* Check whether the CERT is a root certificate. Returns True if this
is the case. */
int
gpgsm_is_root_cert (ksba_cert_t cert)
{
char *issuer;
char *subject;
int yes;
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
yes = is_root_cert (cert, issuer, subject);
xfree (issuer);
xfree (subject);
return yes;
}
/* This is a helper for gpgsm_validate_chain. */
static gpg_error_t
is_cert_still_valid (ctrl_t ctrl, int force_ocsp, int lm, estream_t fp,
ksba_cert_t subject_cert, ksba_cert_t issuer_cert,
int *any_revoked, int *any_no_crl, int *any_crl_too_old)
{
gpg_error_t err;
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
{
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK,
gpg_error (GPG_ERR_NOT_ENABLED));
return 0;
}
err = gpgsm_dirmngr_isvalid (ctrl,
subject_cert, issuer_cert,
force_ocsp? 2 : !!ctrl->use_ocsp);
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err);
if (err)
{
if (!lm)
gpgsm_cert_log_name (NULL, subject_cert);
switch (gpg_err_code (err))
{
case GPG_ERR_CERT_REVOKED:
do_list (1, lm, fp, _("certificate has been revoked"));
*any_revoked = 1;
/* Store that in the keybox so that key listings are able to
return the revoked flag. We don't care about error,
though. */
keydb_set_cert_flags (ctrl, subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0,
~0, VALIDITY_REVOKED);
break;
case GPG_ERR_NO_CRL_KNOWN:
do_list (1, lm, fp, _("no CRL found for certificate"));
*any_no_crl = 1;
break;
case GPG_ERR_NO_DATA:
do_list (1, lm, fp, _("the status of the certificate is unknown"));
*any_no_crl = 1;
break;
case GPG_ERR_CRL_TOO_OLD:
do_list (1, lm, fp, _("the available CRL is too old"));
if (!lm)
log_info (_("please make sure that the "
"\"dirmngr\" is properly installed\n"));
*any_crl_too_old = 1;
break;
default:
do_list (1, lm, fp, _("checking the CRL failed: %s"),
gpg_strerror (err));
return err;
}
}
return 0;
}
/* Helper for gpgsm_validate_chain to check the validity period of
SUBJECT_CERT. The caller needs to pass EXPTIME which will be
updated to the nearest expiration time seen. A DEPTH of 0 indicates
the target certificate, -1 the final root certificate and other
values intermediate certificates. */
static gpg_error_t
check_validity_period (ksba_isotime_t current_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (*not_after)
{
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
}
if (*not_before && strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*not_after && strcmp (current_time, not_after) > 0 )
{
do_list (opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ? _("certificate has expired") :
depth == -1 ? _("root certificate has expired") :
/* other */ _("intermediate certificate has expired"));
if (!listmode)
{
log_info (" (expired at ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* This is a variant of check_validity_period used with the chain
model. The extra constraint here is that notBefore and notAfter
must exists and if the additional argument CHECK_TIME is given this
time is used to check the validity period of SUBJECT_CERT. */
static gpg_error_t
check_validity_period_cm (ksba_isotime_t current_time,
ksba_isotime_t check_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*not_before || !*not_after)
{
do_list (1, listmode, listfp,
_("required certificate attributes missing: %s%s%s"),
!*not_before? "notBefore":"",
(!*not_before && !*not_after)? ", ":"",
!*not_before? "notAfter":"");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (strcmp (not_before, not_after) > 0 )
{
do_list (1, listmode, listfp,
_("certificate with invalid validity"));
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (" expired at ");
dump_isotime (not_after);
log_printf (")\n");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
if (strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*check_time
&& (strcmp (check_time, not_before) < 0
|| strcmp (check_time, not_after) > 0))
{
/* Note that we don't need a case for the root certificate
because its own consistency has already been checked. */
do_list(opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ?
_("signature not created during lifetime of certificate") :
depth == 1 ?
_("certificate not created during lifetime of issuer") :
_("intermediate certificate not created during lifetime "
"of issuer"));
if (!listmode)
{
log_info (depth== 0? _(" ( signature created at ") :
/* */ _(" (certificate created at ") );
dump_isotime (check_time);
log_printf (")\n");
log_info (depth==0? _(" (certificate valid from ") :
/* */ _(" ( issuer valid from ") );
dump_isotime (not_before);
log_info (" to ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* Ask the user whether he wants to mark the certificate CERT trusted.
Returns true if the CERT is the trusted. We also check whether the
agent is at all enabled to allow marktrusted and don't call it in
this session again if it is not. */
static int
ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode)
{
static int no_more_questions;
int rc;
char *fpr;
int success = 0;
fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
xfree (fpr);
if (no_more_questions)
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
rc = gpgsm_agent_marktrusted (ctrl, cert);
if (!rc)
{
log_info (_("root certificate has now been marked as trusted\n"));
success = 1;
}
else if (!listmode)
{
gpgsm_dump_cert ("issuer", cert);
log_info ("after checking the fingerprint, you may want "
"to add it manually to the list of trusted certificates.\n");
}
if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED)
{
if (!no_more_questions)
log_info (_("interactive marking as trusted "
"not enabled in gpg-agent\n"));
no_more_questions = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_CANCELED)
{
log_info (_("interactive marking as trusted "
"disabled for this session\n"));
no_more_questions = 1;
}
else
set_already_asked_marktrusted (cert);
return success;
}
/* Validate a chain and optionally return the nearest expiration time
in R_EXPTIME. With LISTMODE set to 1 a special listmode is
activated where only information about the certificate is printed
to LISTFP and no output is send to the usual log stream. If
CHECKTIME_ARG is set, it is used only in the chain model instead of the
current time.
Defined flag bits
VALIDATE_FLAG_NO_DIRMNGR - Do not do any dirmngr isvalid checks.
VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model.
VALIDATE_FLAG_STEED - Check according to the STEED model.
*/
static int
do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
struct rootca_flags_s *rootca_flags)
{
int rc = 0, depth, maxdepth;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = NULL;
ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
ksba_isotime_t current_time;
ksba_isotime_t check_time;
ksba_isotime_t exptime;
int any_expired = 0;
int any_revoked = 0;
int any_no_crl = 0;
int any_crl_too_old = 0;
int any_no_policy_match = 0;
int is_qualified = -1; /* Indicates whether the certificate stems
from a qualified root certificate.
-1 = unknown, 0 = no, 1 = yes. */
chain_item_t chain = NULL; /* A list of all certificates in the chain. */
gnupg_get_isotime (current_time);
gnupg_copy_time (ctrl->current_time, current_time);
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
if (!strcmp (checktime_arg, "19700101T000000"))
{
do_list (1, listmode, listfp,
_("WARNING: creation time of signature not known - "
"assuming current time"));
gnupg_copy_time (check_time, current_time);
}
else
gnupg_copy_time (check_time, checktime_arg);
}
else
*check_time = 0;
if (r_exptime)
*r_exptime = 0;
*exptime = 0;
if (opt.no_chain_validation && !listmode)
{
log_info ("WARNING: bypassing certificate chain validation\n");
return 0;
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (DBG_X509 && !listmode)
gpgsm_dump_cert ("target", cert);
subject_cert = cert;
ksba_cert_ref (subject_cert);
maxdepth = 50;
depth = 0;
for (;;)
{
int is_root;
gpg_error_t istrusted_rc = -1;
/* Put the certificate on our list. */
{
chain_item_t ci;
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
{
rc = gpg_error_from_syserror ();
goto leave;
}
ksba_cert_ref (subject_cert);
ci->cert = subject_cert;
ci->next = chain;
chain = ci;
}
xfree (issuer);
xfree (subject);
issuer = ksba_cert_get_issuer (subject_cert, 0);
subject = ksba_cert_get_subject (subject_cert, 0);
if (!issuer)
{
do_list (1, listmode, listfp, _("no issuer found in certificate"));
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* Is this a self-issued certificate (i.e. the root certificate)? */
is_root = is_root_cert (subject_cert, issuer, subject);
if (is_root)
{
chain->is_root = 1;
/* Check early whether the certificate is listed as trusted.
We used to do this only later but changed it to call the
check right here so that we can access special flags
associated with that specific root certificate. */
if (gpgsm_cert_has_well_known_private_key (subject_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL,
rootca_flags);
audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED,
subject_cert, istrusted_rc);
/* If the chain model extended attribute is used, make sure
that our chain model flag is set. */
if (!(flags & VALIDATE_FLAG_STEED)
&& has_validation_model_chain (subject_cert, listmode, listfp))
rootca_flags->chain_model = 1;
}
/* Check the validity period. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
rc = check_validity_period_cm (current_time, check_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
else
rc = check_validity_period (current_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
any_expired = 1;
else if (rc)
goto leave;
/* Assert that we understand all critical extensions. */
rc = unknown_criticals (subject_cert, listmode, listfp);
if (rc)
goto leave;
/* Do a policy check. */
if (!opt.no_policy_check)
{
rc = check_cert_policy (subject_cert, listmode, listfp);
if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
{
any_no_policy_match = 1;
rc = 1; /* Be on the safe side and set RC. */
}
else if (rc)
goto leave;
}
/* If this is the root certificate we are at the end of the chain. */
if (is_root)
{
if (!istrusted_rc)
; /* No need to check the certificate for a trusted one. */
else if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
{
/* We only check the signature if the certificate is not
trusted for better diagnostics. */
do_list (1, listmode, listfp,
_("self-signed certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", subject_cert);
}
rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
: GPG_ERR_BAD_CERT);
goto leave;
}
if (!rootca_flags->relax)
{
rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp);
if (rc)
goto leave;
}
/* Set the flag for qualified signatures. This flag is
deduced from a list of root certificates allowed for
qualified signatures. */
if (is_qualified == -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
size_t buflen;
char buf[1];
if (!ksba_cert_get_user_data (cert, "is_qualified",
&buf, sizeof (buf),
&buflen) && buflen)
{
/* We already checked this for this certificate,
thus we simply take it from the user data. */
is_qualified = !!*buf;
}
else
{
/* Need to consult the list of root certificates for
qualified signatures. */
err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL);
if (!err)
is_qualified = 1;
else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND)
is_qualified = 0;
else
log_error ("checking the list of qualified "
"root certificates failed: %s\n",
gpg_strerror (err));
if ( is_qualified != -1 )
{
/* Cache the result but don't care too much
about an error. */
buf[0] = !!is_qualified;
err = ksba_cert_set_user_data (subject_cert,
"is_qualified", buf, 1);
if (err)
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
}
}
}
/* Act on the check for a trusted root certificates. */
rc = istrusted_rc;
if (!rc)
;
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
{
do_list (0, listmode, listfp,
_("root certificate is not marked trusted"));
/* If we already figured out that the certificate is
expired it does not make much sense to ask the user
whether they want to trust the root certificate. We
should do this only if the certificate under question
will then be usable. If the certificate has a well
known private key asking the user does not make any
sense. */
if ( !any_expired
&& !gpgsm_cert_has_well_known_private_key (subject_cert)
&& (!listmode || !already_asked_marktrusted (subject_cert))
&& ask_marktrusted (ctrl, subject_cert, listmode) )
rc = 0;
}
else
{
log_error (_("checking the trust list failed: %s\n"),
gpg_strerror (rc));
}
if (rc)
goto leave;
/* Check for revocations etc. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
;
else if ((flags & VALIDATE_FLAG_STEED))
; /* Fixme: check revocations via DNS. */
else if (opt.no_trusted_cert_crl_check || rootca_flags->relax)
;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, subject_cert,
&any_revoked, &any_no_crl,
&any_crl_too_old);
if (rc)
goto leave;
break; /* Okay: a self-signed certificate is an end-point. */
} /* End is_root. */
/* Take care that the chain does not get too long. */
if ((depth+1) > maxdepth)
{
do_list (1, listmode, listfp, _("certificate chain too long\n"));
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, subject_cert, issuer, 0);
if (rc)
{
if (rc == -1)
{
do_list (0, listmode, listfp, _("issuer certificate not found"));
if (!listmode)
{
log_info ("issuer certificate: #/");
gpgsm_dump_string (issuer);
log_printf ("\n");
}
}
else
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
try_another_cert:
if (DBG_X509)
{
log_debug ("got issuer's certificate:\n");
gpgsm_dump_cert ("issuer", issuer_cert);
}
rc = gpgsm_check_cert_sig (issuer_cert, subject_cert);
if (rc)
{
do_list (0, listmode, listfp, _("certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", subject_cert);
}
if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
{
/* We now try to find other issuer certificates which
might have been used. This is required because some
CAs are reusing the issuer and subject DN for new
root certificates. */
/* FIXME: Do this only if we don't have an
AKI.keyIdentifier */
rc = find_up (ctrl, kh, subject_cert, issuer, 1);
if (!rc)
{
ksba_cert_t tmp_cert;
rc = keydb_get_cert (kh, &tmp_cert);
if (rc || !compare_certs (issuer_cert, tmp_cert))
{
/* The find next did not work or returned an
identical certificate. We better stop here
to avoid infinite checks. */
/* No need to set RC because it is not used:
rc = gpg_error (GPG_ERR_BAD_SIGNATURE); */
ksba_cert_release (tmp_cert);
}
else
{
do_list (0, listmode, listfp,
_("found another possible matching "
"CA certificate - trying again"));
ksba_cert_release (issuer_cert);
issuer_cert = tmp_cert;
goto try_another_cert;
}
}
}
/* We give a more descriptive error code than the one
returned from the signature checking. */
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
is_root = gpgsm_is_root_cert (issuer_cert);
istrusted_rc = -1;
/* Check that a CA is allowed to issue certificates. */
{
int chainlen;
rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp);
if (rc)
{
/* Not allowed. Check whether this is a trusted root
certificate and whether we allow special exceptions.
We could carry the result of the test over to the
regular root check at the top of the loop but for
clarity we won't do that. Given that the majority of
certificates carry proper BasicContraints our way of
overriding an error in the way is justified for
performance reasons. */
if (is_root)
{
if (gpgsm_cert_has_well_known_private_key (issuer_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted
(ctrl, issuer_cert, NULL, rootca_flags);
if (!istrusted_rc && rootca_flags->relax)
{
/* Ignore the error due to the relax flag. */
rc = 0;
chainlen = -1;
}
}
}
if (rc)
goto leave;
if (chainlen >= 0 && depth > chainlen)
{
do_list (1, listmode, listfp,
_("certificate chain longer than allowed by CA (%d)"),
chainlen);
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
}
/* Is the certificate allowed to sign other certificates. */
if (!listmode)
{
rc = gpgsm_cert_use_cert_p (issuer_cert);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage",
numbuf, NULL);
goto leave;
}
}
/* Check for revocations etc. Note that for a root certificate
this test is done a second time later. This should eventually
be fixed. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
rc = 0;
else if ((flags & VALIDATE_FLAG_STEED))
rc = 0; /* Fixme: XXX */
else if (is_root && (opt.no_trusted_cert_crl_check
|| (!istrusted_rc && rootca_flags->relax)))
rc = 0;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, issuer_cert,
&any_revoked, &any_no_crl, &any_crl_too_old);
if (rc)
goto leave;
if (opt.verbose && !listmode)
log_info (depth == 0 ? _("certificate is good\n") :
!is_root ? _("intermediate certificate is good\n") :
/* other */ _("root certificate is good\n"));
/* Under the chain model the next check time is the creation
time of the subject certificate. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
rc = ksba_cert_get_validity (subject_cert, 0, check_time);
if (rc)
{
/* That will never happen as we have already checked
this above. */
BUG ();
}
}
/* For the next round the current issuer becomes the new subject. */
keydb_search_reset (kh);
ksba_cert_release (subject_cert);
subject_cert = issuer_cert;
issuer_cert = NULL;
depth++;
} /* End chain traversal. */
if (!listmode && !opt.quiet)
{
if (opt.no_policy_check)
log_info ("policies not checked due to %s option\n",
"--disable-policy-checks");
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
log_info ("CRLs not checked due to %s option\n",
ctrl->offline ? "offline" : "--disable-crl-checks");
}
if (!rc)
{ /* If we encountered an error somewhere during the checks, set
the error code to the most critical one */
if (any_revoked)
rc = gpg_error (GPG_ERR_CERT_REVOKED);
else if (any_expired)
rc = gpg_error (GPG_ERR_CERT_EXPIRED);
else if (any_no_crl)
rc = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (any_crl_too_old)
rc = gpg_error (GPG_ERR_CRL_TOO_OLD);
else if (any_no_policy_match)
rc = gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
leave:
/* If we have traversed a complete chain up to the root we will
reset the ephemeral flag for all these certificates. This is done
regardless of any error because those errors may only be
transient. */
if (chain && chain->is_root)
{
gpg_error_t err;
chain_item_t ci;
for (ci = chain; ci; ci = ci->next)
{
/* Note that it is possible for the last certificate in the
chain (i.e. our target certificate) that it has not yet
been stored in the keybox and thus the flag can't be set.
We ignore this error because it will later be stored
anyway. */
err = keydb_set_cert_flags (ctrl, ci->cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (err));
}
}
/* If we have figured something about the qualified signature
capability of the certificate under question, store the result as
user data in all certificates of the chain. We do this even if the
validation itself failed. */
if (is_qualified != -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
chain_item_t ci;
char buf[1];
buf[0] = !!is_qualified;
for (ci = chain; ci; ci = ci->next)
{
err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1);
if (err)
{
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
if (!rc)
rc = err;
}
}
}
/* If auditing has been enabled, record what is in the chain. */
if (ctrl->audit)
{
chain_item_t ci;
audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN);
for (ci = chain; ci; ci = ci->next)
{
audit_log_cert (ctrl->audit,
ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT,
ci->cert, 0);
}
audit_log (ctrl->audit, AUDIT_CHAIN_END);
}
if (r_exptime)
gnupg_copy_time (r_exptime, exptime);
xfree (issuer);
xfree (subject);
keydb_release (kh);
while (chain)
{
chain_item_t ci_next = chain->next;
ksba_cert_release (chain->cert);
xfree (chain);
chain = ci_next;
}
ksba_cert_release (issuer_cert);
ksba_cert_release (subject_cert);
return rc;
}
/* Validate a certificate chain. For a description see
do_validate_chain. This function is a wrapper to handle a root
certificate with the chain_model flag set. If RETFLAGS is not
NULL, flags indicating now the verification was done are stored
there. The only defined vits for RETFLAGS are
VALIDATE_FLAG_CHAIN_MODEL and VALIDATE_FLAG_STEED.
If you are verifying a signature you should set CHECKTIME to the
creation time of the signature. If your are verifying a
certificate, set it nil (i.e. the empty string). If the creation
date of the signature is not known use the special date
"19700101T000000" which is treated in a special way here. */
int
gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
unsigned int *retflags)
{
int rc;
struct rootca_flags_s rootca_flags;
unsigned int dummy_retflags;
if (!retflags)
retflags = &dummy_retflags;
/* If the session requested a certain validation mode make sure the
corresponding flags are set. */
if (ctrl->validation_model == 1)
flags |= VALIDATE_FLAG_CHAIN_MODEL;
else if (ctrl->validation_model == 2)
flags |= VALIDATE_FLAG_STEED;
/* If the chain model was forced, set this immediately into
RETFLAGS. */
*retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL);
memset (&rootca_flags, 0, sizeof rootca_flags);
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp, flags,
&rootca_flags);
if (!rc && (flags & VALIDATE_FLAG_STEED))
{
*retflags |= VALIDATE_FLAG_STEED;
}
else if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED
&& !(flags & VALIDATE_FLAG_CHAIN_MODEL)
&& (rootca_flags.valid && rootca_flags.chain_model))
{
do_list (0, listmode, listfp, _("switching to chain model"));
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp,
(flags |= VALIDATE_FLAG_CHAIN_MODEL),
&rootca_flags);
*retflags |= VALIDATE_FLAG_CHAIN_MODEL;
}
if (opt.verbose)
do_list (0, listmode, listfp, _("validation model used: %s"),
(*retflags & VALIDATE_FLAG_STEED)?
"steed" :
(*retflags & VALIDATE_FLAG_CHAIN_MODEL)?
_("chain"):_("shell"));
return rc;
}
/* Check that the given certificate is valid but DO NOT check any
constraints. We assume that the issuers certificate is already in
the DB and that this one is valid; which it should be because it
has been checked using this function. */
int
gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert)
{
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh;
ksba_cert_t issuer_cert = NULL;
if (opt.no_chain_validation)
{
log_info ("WARNING: bypassing basic certificate checks\n");
return 0;
}
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (cert, issuer, subject))
{
rc = gpgsm_check_cert_sig (cert, cert);
if (rc)
{
log_error ("self-signed certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
}
else
{
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, cert, issuer, 0);
if (rc)
{
if (rc == -1)
{
log_info ("issuer certificate (#/");
gpgsm_dump_string (issuer);
log_printf (") not found\n");
}
else
log_error ("failed to find issuer's certificate: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gpgsm_check_cert_sig (issuer_cert, cert);
if (rc)
{
log_error ("certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (opt.verbose)
log_info (_("certificate is good\n"));
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
ksba_cert_release (issuer_cert);
return rc;
}
/* Check whether the certificate CERT has been issued by the German
authority for qualified signature. They do not set the
basicConstraints and thus we need this workaround. It works by
looking up the root certificate and checking whether that one is
listed as a qualified certificate for Germany.
We also try to cache this data but as long as don't keep a
reference to the certificate this won't be used.
Returns: True if CERT is a RegTP issued CA cert (i.e. the root
certificate itself or one of the CAs). In that case CHAINLEN will
receive the length of the chain which is either 0 or 1.
*/
static int
get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen)
{
gpg_error_t err;
ksba_cert_t next;
int rc = 0;
int i, depth;
char country[3];
ksba_cert_t array[4];
char buf[2];
size_t buflen;
int dummy_chainlen;
if (!chainlen)
chainlen = &dummy_chainlen;
*chainlen = 0;
err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen",
&buf, sizeof (buf), &buflen);
if (!err)
{
/* Got info. */
if (buflen < 2 || !*buf)
return 0; /* Nothing found. */
*chainlen = buf[1];
return 1; /* This is a regtp CA. */
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_error ("ksba_cert_get_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
return 0; /* Nothing found. */
}
/* Need to gather the info. This requires to walk up the chain
until we have found the root. Because we are only interested in
German Bundesnetzagentur (former RegTP) derived certificates 3
levels are enough. (The German signature law demands a 3 tier
hierarchy; thus there is only one CA between the EE and the Root
CA.) */
memset (&array, 0, sizeof array);
depth = 0;
ksba_cert_ref (cert);
array[depth++] = cert;
ksba_cert_ref (cert);
while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next)))
{
ksba_cert_release (cert);
ksba_cert_ref (next);
array[depth++] = next;
cert = next;
}
ksba_cert_release (cert);
if (rc != -1 || !depth || depth == DIM(array) )
{
/* We did not reached the root. */
goto leave;
}
/* If this is a German signature law issued certificate, we store
additional information. */
if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country)
&& !strcmp (country, "de"))
{
/* Setting the pathlen for the root CA and the CA flag for the
next one is all what we need to do. */
err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen",
"\x01\x01", 2);
if (!err && depth > 1)
err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen",
"\x01\x00", 2);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
*chainlen = (depth>1? 0:1);
return 1;
}
leave:
/* Nothing special with this certificate. Mark the target
certificate anyway to avoid duplicate lookups. */
err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
return 0;
}
diff --git a/sm/export.c b/sm/export.c
index 918096e7c..f9efe2970 100644
--- a/sm/export.c
+++ b/sm/export.c
@@ -1,754 +1,754 @@
/* export.c - Export certificates and private keys.
* Copyright (C) 2002, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/exechelp.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "minip12.h"
/* A table to store a fingerprint as used in a duplicates table. We
don't need to hash here because a fingerprint is already a perfect
hash value. This we use the most significant bits to index the
table and then use a linked list for the overflow. Possible
enhancement for very large number of certificates: Add a second
level table and then resort to a linked list. */
struct duptable_s
{
struct duptable_s *next;
/* Note that we only need to store 19 bytes because the first byte
- is implictly given by the table index (we require at least 8
+ is implicitly given by the table index (we require at least 8
bits). */
unsigned char fpr[19];
};
typedef struct duptable_s *duptable_t;
#define DUPTABLE_BITS 12
#define DUPTABLE_SIZE (1 << DUPTABLE_BITS)
static void print_short_info (ksba_cert_t cert, estream_t stream);
static gpg_error_t export_p12 (ctrl_t ctrl,
const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip,
int rawmode,
void **r_result, size_t *r_resultlen);
/* Create a table used to indetify duplicated certificates. */
static duptable_t *
create_duptable (void)
{
return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t));
}
static void
destroy_duptable (duptable_t *table)
{
int idx;
duptable_t t, t2;
if (table)
{
for (idx=0; idx < DUPTABLE_SIZE; idx++)
for (t = table[idx]; t; t = t2)
{
t2 = t->next;
xfree (t);
}
xfree (table);
}
}
/* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true
if the fingerprint already exists in the table. */
static gpg_error_t
insert_duptable (duptable_t *table, unsigned char *fpr, int *exists)
{
size_t idx;
duptable_t t;
*exists = 0;
idx = fpr[0];
#if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8
#error cannot handle a table larger than 16 bits or smaller than 8 bits
#elif DUPTABLE_BITS > 8
idx <<= (DUPTABLE_BITS - 8);
idx |= (fpr[1] & ~(~0U << 4));
#endif
for (t = table[idx]; t; t = t->next)
if (!memcmp (t->fpr, fpr+1, 19))
break;
if (t)
{
*exists = 1;
return 0;
}
/* Insert that fingerprint. */
t = xtrymalloc (sizeof *t);
if (!t)
return gpg_error_from_syserror ();
memcpy (t->fpr, fpr+1, 19);
t->next = table[idx];
table[idx] = t;
return 0;
}
/* Export all certificates or just those given in NAMES. The output
is written to STREAM. */
void
gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream)
{
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
strlist_t sl;
ksba_cert_t cert = NULL;
int rc=0;
int count = 0;
int i;
duptable_t *dtable;
dtable = create_duptable ();
if (!dtable)
{
log_error ("creating duplicates table failed: %s\n", strerror (errno));
goto leave;
}
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are exported. */
if (names && ndesc)
{
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
keydb_set_ephemeral (hd, 1);
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned char fpr[20];
int exists;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
rc = insert_duptable (dtable, fpr, &exists);
if (rc)
{
log_error ("inserting into duplicates table failed: %s\n",
gpg_strerror (rc));
goto leave;
}
if (!exists && count && !ctrl->create_pem)
{
log_info ("exporting more than one certificate "
"is not possible in binary mode\n");
log_info ("ignoring other certificates\n");
break;
}
if (!exists)
{
const unsigned char *image;
size_t imagelen;
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
if (count)
es_putc ('\n', stream);
print_short_info (cert, stream);
es_putc ('\n', stream);
}
count++;
if (!b64writer)
{
ctrl->pem_name = "CERTIFICATE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 :0)),
ctrl->pem_name, stream, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
rc = ksba_writer_write (writer, image, imagelen);
if (rc)
{
log_error ("write error: %s\n", gpg_strerror (rc));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
}
ksba_cert_release (cert);
cert = NULL;
}
if (rc && rc != -1)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
else if (b64writer)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
}
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
destroy_duptable (dtable);
}
/* Export a certificate and its private key. RAWMODE controls the
actual output:
0 - Private key and certifciate in PKCS#12 format
1 - Only unencrypted private key in PKCS#8 format
2 - Only unencrypted private key in PKCS#1 format
*/
void
gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode)
{
gpg_error_t err = 0;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cert_t cert = NULL;
const unsigned char *image;
size_t imagelen;
char *keygrip = NULL;
char *prompt;
void *data;
size_t datalen;
hd = keydb_new ();
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
desc = xtrycalloc (1, sizeof *desc);
if (!desc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
err = classify_user_id (name, desc, 0);
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
/* Lookup the certificate and make sure that it is unique. */
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
err = keydb_get_cert (hd, &cert);
if (err)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (err));
goto leave;
}
next_ambiguous:
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
ksba_cert_t cert2 = NULL;
if (!keydb_get_cert (hd, &cert2))
{
if (gpgsm_certs_identical_p (cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
else if (err == -1 || gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
}
keygrip = gpgsm_get_keygrip_hexstring (cert);
if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip))
{
/* Note, that the !keygrip case indicates a bad certificate. */
err = gpg_error (GPG_ERR_NO_SECKEY);
log_error ("can't export key '%s': %s\n", name, gpg_strerror (err));
goto leave;
}
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
print_short_info (cert, stream);
es_putc ('\n', stream);
}
if (opt.p12_charset && ctrl->create_pem && !rawmode)
{
es_fprintf (stream, "The passphrase is %s encoded.\n\n",
opt.p12_charset);
}
if (rawmode == 0)
ctrl->pem_name = "PKCS12";
else if (rawmode == 1)
ctrl->pem_name = "PRIVATE KEY";
else
ctrl->pem_name = "RSA PRIVATE KEY";
err = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, stream, &writer);
if (err)
{
log_error ("can't create writer: %s\n", gpg_strerror (err));
goto leave;
}
prompt = gpgsm_format_keydesc (cert);
err = export_p12 (ctrl, image, imagelen, prompt, keygrip, rawmode,
&data, &datalen);
xfree (prompt);
if (err)
goto leave;
err = ksba_writer_write (writer, data, datalen);
xfree (data);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
err = gnupg_ksba_finish_writer (b64writer);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
ksba_cert_release (cert);
cert = NULL;
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (keygrip);
xfree (desc);
keydb_release (hd);
}
/* Print some info about the certifciate CERT to FP or STREAM */
static void
print_short_info (ksba_cert_t cert, estream_t stream)
{
char *p;
ksba_sexp_t sexp;
int idx;
for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs ((!idx
? "Issuer ...: "
: "\n aka ...: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
es_fputs ("Serial ...: ", stream);
sexp = ksba_cert_get_serial (cert);
if (sexp)
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
es_write_hexstring (stream, s+1, len, 0, NULL);
}
xfree (sexp);
}
es_putc ('\n', stream);
for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs ((!idx
? "Subject ..: "
: "\n aka ..: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
es_fprintf (stream, "Keygrip ..: %s\n", p);
xfree (p);
}
}
/* Parse a private key S-expression and return a malloced array with
the RSA parameters in pkcs#12 order. The caller needs to
deep-release this array. */
static gcry_mpi_t *
sexp_to_kparms (gcry_sexp_t sexp)
{
gcry_sexp_t list, l2;
const char *name;
const char *s;
size_t n;
int idx;
const char *elems;
gcry_mpi_t *array;
list = gcry_sexp_find_token (sexp, "private-key", 0 );
if(!list)
return NULL;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if(!name || n != 3 || memcmp (name, "rsa", 3))
{
gcry_sexp_release (list);
return NULL;
}
/* Parameter names used with RSA in the pkcs#12 order. */
elems = "nedqp--u";
array = xtrycalloc (strlen(elems) + 1, sizeof *array);
if (!array)
{
gcry_sexp_release (list);
return NULL;
}
for (idx=0, s=elems; *s; s++, idx++ )
{
if (*s == '-')
continue; /* Computed below */
l2 = gcry_sexp_find_token (list, s, 1);
if (l2)
{
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
}
if (!array[idx]) /* Required parameter not found or invalid. */
{
for (idx=0; array[idx]; idx++)
gcry_mpi_release (array[idx]);
xfree (array);
gcry_sexp_release (list);
return NULL;
}
}
gcry_sexp_release (list);
array[5] = gcry_mpi_snew (0); /* compute d mod (q-1) */
gcry_mpi_sub_ui (array[5], array[3], 1);
gcry_mpi_mod (array[5], array[2], array[5]);
array[6] = gcry_mpi_snew (0); /* compute d mod (p-1) */
gcry_mpi_sub_ui (array[6], array[4], 1);
gcry_mpi_mod (array[6], array[2], array[6]);
return array;
}
static gpg_error_t
export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip, int rawmode,
void **r_result, size_t *r_resultlen)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_skey = NULL;
gcry_mpi_t *kparms = NULL;
unsigned char *key = NULL;
size_t keylen;
char *passphrase = NULL;
unsigned char *result = NULL;
size_t resultlen;
int i;
*r_result = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Receive the wrapped key from the agent. */
err = gpgsm_agent_export_key (ctrl, keygrip, prompt,
&wrappedkey, &wrappedkeylen);
if (err)
goto leave;
/* Unwrap the key. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
xfree (wrappedkey);
wrappedkey = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Convert to a gcrypt S-expression. */
err = gcry_sexp_create (&s_skey, key, keylen, 0, xfree_fnc);
if (err)
goto leave;
key = NULL; /* Key is now owned by S_KEY. */
/* Get the parameters from the S-expression. */
kparms = sexp_to_kparms (s_skey);
gcry_sexp_release (s_skey);
s_skey = NULL;
if (!kparms)
{
log_error ("error converting key parameters\n");
err = GPG_ERR_BAD_SECKEY;
goto leave;
}
if (rawmode)
{
/* Export in raw mode, that is only the pkcs#1/#8 private key. */
result = p12_raw_build (kparms, rawmode, &resultlen);
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 ("Please enter the passphrase to protect the "
"new PKCS#12 object."),
1, &passphrase);
if (err)
goto leave;
result = p12_build (kparms, certimg, certimglen, passphrase,
opt.p12_charset, &resultlen);
xfree (passphrase);
passphrase = NULL;
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (key);
gcry_sexp_release (s_skey);
if (kparms)
{
for (i=0; kparms[i]; i++)
gcry_mpi_release (kparms[i]);
xfree (kparms);
}
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{
/* During export this is the passphrase used to unprotect the
key and not the pkcs#12 thing as in export. Therefore we can
issue the regular passphrase status. FIXME: replace the all
zero keyid by a regular one. */
gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000");
}
if (err)
{
xfree (result);
}
else
{
*r_result = result;
*r_resultlen = resultlen;
}
return err;
}
diff --git a/sm/fingerprint.c b/sm/fingerprint.c
index 4bf378a1c..b7a9c3f47 100644
--- a/sm/fingerprint.c
+++ b/sm/fingerprint.c
@@ -1,409 +1,409 @@
/* fingerprint.c - Get the fingerprint
* Copyright (C) 2001 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "../common/host2net.h"
/* Return the fingerprint of the certificate (we can't put this into
libksba because we need libgcrypt support). The caller must
provide an array of sufficient length or NULL so that the function
allocates the array. If r_len is not NULL, the length of the
digest is returned; well, this can also be done by using
gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used.
If there is a problem , the function does never return NULL but a
digest of all 0xff.
*/
unsigned char *
gpgsm_get_fingerprint (ksba_cert_t cert, int algo,
unsigned char *array, int *r_len)
{
gcry_md_hd_t md;
int rc, len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len);
if (!array)
array = xmalloc (len);
if (r_len)
*r_len = len;
/* Fist check whether we have cached the fingerprint. */
if (algo == GCRY_MD_SHA1)
{
size_t buflen;
assert (len >= 20);
if (!ksba_cert_get_user_data (cert, "sha1-fingerprint",
array, len, &buflen)
&& buflen == 20)
return array;
}
/* No, need to compute it. */
rc = gcry_md_open (&md, algo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
memset (array, 0xff, len); /* better return an invalid fpr than NULL */
return array;
}
rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (rc)
{
log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
memset (array, 0xff, len); /* better return an invalid fpr than NULL */
return array;
}
gcry_md_final (md);
memcpy (array, gcry_md_read(md, algo), len );
gcry_md_close (md);
/* Cache an SHA-1 fingerprint. */
if ( algo == GCRY_MD_SHA1 )
ksba_cert_set_user_data (cert, "sha1-fingerprint", array, 20);
return array;
}
/* Return an allocated buffer with the formatted fingerprint */
char *
gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo)
{
unsigned char digest[MAX_DIGEST_LEN];
char *buf;
int len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len <= MAX_DIGEST_LEN );
gpgsm_get_fingerprint (cert, algo, digest, NULL);
buf = xmalloc (len*3+1);
bin2hexcolon (digest, len, buf);
return buf;
}
/* Return an allocated buffer with the formatted fingerprint as one
large hexnumber */
char *
gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo)
{
unsigned char digest[MAX_DIGEST_LEN];
char *buf;
int len;
if (!algo)
algo = GCRY_MD_SHA1;
len = gcry_md_get_algo_dlen (algo);
assert (len <= MAX_DIGEST_LEN );
gpgsm_get_fingerprint (cert, algo, digest, NULL);
buf = xmalloc (len*2+1);
bin2hex (digest, len, buf);
return buf;
}
/* Return a certificate ID. These are the last 4 bytes of the SHA-1
fingerprint. If R_HIGH is not NULL the next 4 bytes are stored
there. */
unsigned long
gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high)
{
unsigned char digest[20];
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL);
if (r_high)
*r_high = buf32_to_ulong (digest+12);
return buf32_to_ulong (digest + 16);
}
/* Return the so called KEYGRIP which is the SHA-1 hash of the public
- key parameters expressed as an canoncial encoded S-Exp. ARRAY must
+ key parameters expressed as an canonical encoded S-Exp. ARRAY must
be 20 bytes long. Returns ARRAY or a newly allocated buffer if ARRAY was
given as NULL. May return NULL on error. */
unsigned char *
gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array)
{
gcry_sexp_t s_pkey;
int rc;
ksba_sexp_t p;
size_t n;
p = ksba_cert_get_public_key (cert);
if (!p)
return NULL; /* oops */
if (DBG_X509)
log_debug ("get_keygrip for public key\n");
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
return NULL;
}
rc = gcry_sexp_sscan ( &s_pkey, NULL, (char*)p, n);
xfree (p);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
return NULL;
}
array = gcry_pk_get_keygrip (s_pkey, array);
gcry_sexp_release (s_pkey);
if (!array)
{
log_error ("can't calculate keygrip\n");
return NULL;
}
if (DBG_X509)
log_printhex (array, 20, "keygrip=");
return array;
}
/* Return an allocated buffer with the keygrip of CERT encoded as a
hexstring. NULL is returned in case of error. */
char *
gpgsm_get_keygrip_hexstring (ksba_cert_t cert)
{
unsigned char grip[20];
char *buf;
if (!gpgsm_get_keygrip (cert, grip))
return NULL;
buf = xtrymalloc (20*2+1);
if (buf)
bin2hex (grip, 20, buf);
return buf;
}
/* Return the PK algorithm used by CERT as well as the length in bits
of the public key at NBITS. */
int
gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits)
{
gcry_sexp_t s_pkey;
int rc;
ksba_sexp_t p;
size_t n;
gcry_sexp_t l1, l2;
const char *name;
char namebuf[128];
if (nbits)
*nbits = 0;
p = ksba_cert_get_public_key (cert);
if (!p)
return 0;
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
xfree (p);
return 0;
}
rc = gcry_sexp_sscan (&s_pkey, NULL, (char *)p, n);
xfree (p);
if (rc)
return 0;
if (nbits)
*nbits = gcry_pk_get_nbits (s_pkey);
/* Breaking the algorithm out of the S-exp is a bit of a challenge ... */
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
{
gcry_sexp_release (s_pkey);
return 0;
}
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l2;
name = gcry_sexp_nth_data (l1, 0, &n);
if (name)
{
if (n > sizeof namebuf -1)
n = sizeof namebuf -1;
memcpy (namebuf, name, n);
namebuf[n] = 0;
}
else
*namebuf = 0;
gcry_sexp_release (l1);
gcry_sexp_release (s_pkey);
return gcry_pk_map_name (namebuf);
}
/* If KEY is an RSA key, return its modulus. For non-RSA keys or on
* error return NULL. */
gcry_mpi_t
gpgsm_get_rsa_modulus (ksba_cert_t cert)
{
gpg_error_t err;
gcry_sexp_t key;
gcry_sexp_t list = NULL;
gcry_sexp_t l2 = NULL;
char *name = NULL;
gcry_mpi_t modulus = NULL;
{
ksba_sexp_t ckey;
size_t n;
ckey = ksba_cert_get_public_key (cert);
if (!ckey)
return NULL;
n = gcry_sexp_canon_len (ckey, 0, NULL, NULL);
if (!n)
{
xfree (ckey);
return NULL;
}
err = gcry_sexp_sscan (&key, NULL, (char *)ckey, n);
xfree (ckey);
if (err)
return NULL;
}
list = gcry_sexp_find_token (key, "public-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "protected-private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
gcry_sexp_release (key);
if (!list)
return NULL; /* No suitable key. */
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
l2 = NULL;
name = gcry_sexp_nth_string (list, 0);
if (!name)
;
else if (gcry_pk_map_name (name) == GCRY_PK_RSA)
{
l2 = gcry_sexp_find_token (list, "n", 1);
if (l2)
modulus = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
}
gcry_free (name);
gcry_sexp_release (l2);
gcry_sexp_release (list);
return modulus;
}
/* For certain purposes we need a certificate id which has an upper
limit of the size. We use the hash of the issuer name and the
serial number for this. In most cases the serial number is not
that large and the resulting string can be passed on an assuan
command line. Everything is hexencoded with the serialnumber
delimited from the hash by a dot.
The caller must free the string.
*/
char *
gpgsm_get_certid (ksba_cert_t cert)
{
ksba_sexp_t serial;
char *p;
char *endp;
unsigned char hash[20];
unsigned long n;
char *certid;
int i;
p = ksba_cert_get_issuer (cert, 0);
if (!p)
return NULL; /* Ooops: No issuer */
gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p));
xfree (p);
serial = ksba_cert_get_serial (cert);
if (!serial)
return NULL; /* oops: no serial number */
p = (char *)serial;
if (*p != '(')
{
log_error ("Ooops: invalid serial number\n");
xfree (serial);
return NULL;
}
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p != ':')
{
log_error ("Ooops: invalid serial number (no colon)\n");
xfree (serial);
return NULL;
}
p++;
certid = xtrymalloc ( 40 + 1 + n*2 + 1);
if (!certid)
{
xfree (serial);
return NULL; /* out of core */
}
for (i=0, endp = certid; i < 20; i++, endp += 2 )
sprintf (endp, "%02X", hash[i]);
*endp++ = '.';
for (i=0; i < n; i++, endp += 2)
sprintf (endp, "%02X", ((unsigned char*)p)[i]);
*endp = 0;
xfree (serial);
return certid;
}
diff --git a/sm/sign.c b/sm/sign.c
index 341d8cf68..dbedb271c 100644
--- a/sm/sign.c
+++ b/sm/sign.c
@@ -1,942 +1,942 @@
/* sign.c - Sign a message
* Copyright (C) 2001, 2002, 2003, 2008,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/i18n.h"
/* Hash the data and return if something was hashed. Return -1 on error. */
static int
hash_data (int fd, gcry_md_hd_t md)
{
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return -1;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
gcry_md_write (md, buffer, nread);
}
while (nread);
if (es_ferror (fp))
{
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
rc = -1;
}
es_fclose (fp);
return rc;
}
static int
hash_and_copy_data (int fd, gcry_md_hd_t md, ksba_writer_t writer)
{
gpg_error_t err;
estream_t fp;
char buffer[4096];
int nread;
int rc = 0;
int any = 0;
fp = es_fdopen_nc (fd, "rb");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno));
return tmperr;
}
do
{
nread = es_fread (buffer, 1, DIM(buffer), fp);
if (nread)
{
any = 1;
gcry_md_write (md, buffer, nread);
err = ksba_writer_write_octet_string (writer, buffer, nread, 0);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
rc = err;
}
}
}
while (nread && !rc);
if (es_ferror (fp))
{
rc = gpg_error_from_syserror ();
log_error ("read error on fd %d: %s\n", fd, strerror (errno));
}
es_fclose (fp);
if (!any)
{
/* We can't allow signing an empty message because it does not
make much sense and more seriously, ksba_cms_build has
already written the tag for data and now expects an octet
string and an octet string of size 0 is illegal. */
log_error ("cannot sign an empty message\n");
rc = gpg_error (GPG_ERR_NO_DATA);
}
if (!rc)
{
err = ksba_writer_write_octet_string (writer, NULL, 0, 1);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
rc = err;
}
}
return rc;
}
/* Get the default certificate which is defined as the first
certificate capable of signing returned by the keyDB and has a
secret key available. */
int
gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert)
{
KEYDB_HANDLE hd;
ksba_cert_t cert = NULL;
int rc;
char *p;
hd = keydb_new ();
if (!hd)
return gpg_error (GPG_ERR_GENERAL);
rc = keydb_search_first (ctrl, hd);
if (rc)
{
keydb_release (hd);
return rc;
}
do
{
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
keydb_release (hd);
return rc;
}
if (!gpgsm_cert_use_sign_p (cert, 1))
{
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
if (!gpgsm_agent_havekey (ctrl, p))
{
xfree (p);
keydb_release (hd);
*r_cert = cert;
return 0; /* got it */
}
xfree (p);
}
}
ksba_cert_release (cert);
cert = NULL;
}
while (!(rc = keydb_search_next (ctrl, hd)));
if (rc && rc != -1)
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
ksba_cert_release (cert);
keydb_release (hd);
return rc;
}
static ksba_cert_t
get_default_signer (ctrl_t ctrl)
{
KEYDB_SEARCH_DESC desc;
ksba_cert_t cert = NULL;
KEYDB_HANDLE kh = NULL;
int rc;
if (!opt.local_user)
{
rc = gpgsm_get_default_cert (ctrl, &cert);
if (rc)
{
if (rc != -1)
log_debug ("failed to find default certificate: %s\n",
gpg_strerror (rc));
return NULL;
}
return cert;
}
rc = classify_user_id (opt.local_user, &desc, 0);
if (rc)
{
log_error ("failed to find default signer: %s\n", gpg_strerror (rc));
return NULL;
}
kh = keydb_new ();
if (!kh)
return NULL;
rc = keydb_search (ctrl, kh, &desc, 1);
if (rc)
{
log_debug ("failed to find default certificate: rc=%d\n", rc);
}
else
{
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_debug ("failed to get cert: rc=%d\n", rc);
}
}
keydb_release (kh);
return cert;
}
/* Depending on the options in CTRL add the certificate CERT as well as
other certificate up in the chain to the Root-CA to the CMS
object. */
static int
add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert)
{
gpg_error_t err;
int rc = 0;
ksba_cert_t next = NULL;
int n;
int not_root = 0;
ksba_cert_ref (cert);
n = ctrl->include_certs;
log_debug ("adding certificates at level %d\n", n);
if (n == -2)
{
not_root = 1;
n = -1;
}
if (n < 0 || n > 50)
n = 50; /* We better apply an upper bound */
/* First add my own certificate unless we don't want any certificate
included at all. */
if (n)
{
if (not_root && gpgsm_is_root_cert (cert))
err = 0;
else
err = ksba_cms_add_cert (cms, cert);
if (err)
goto ksba_failure;
if (n>0)
n--;
}
/* Walk the chain to include all other certificates. Note that a -1
used for N makes sure that there is no limit and all certs get
included. */
while ( n-- && !(rc = gpgsm_walk_cert_chain (ctrl, cert, &next)) )
{
if (not_root && gpgsm_is_root_cert (next))
err = 0;
else
err = ksba_cms_add_cert (cms, next);
ksba_cert_release (cert);
cert = next; next = NULL;
if (err)
goto ksba_failure;
}
ksba_cert_release (cert);
return rc == -1? 0: rc;
ksba_failure:
ksba_cert_release (cert);
log_error ("ksba_cms_add_cert failed: %s\n", gpg_strerror (err));
return err;
}
#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */
static gpg_error_t
add_signed_attribute (ksba_cms_t cms, const char *attrstr)
{
gpg_error_t err;
char **fields = NULL;
const char *s;
int i;
unsigned char *der = NULL;
size_t derlen;
fields = strtokenize (attrstr, ":");
if (!fields)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
goto leave;
}
for (i=0; fields[i]; i++)
;
if (i != 3)
{
err = gpg_error (GPG_ERR_SYNTAX);
log_error ("invalid attribute specification '%s': %s\n",
attrstr, i < 3 ? "not enough fields":"too many fields");
goto leave;
}
if (!ascii_strcasecmp (fields[1], "u"))
{
err = 0;
- goto leave; /* Skip unsigned attruibutes. */
+ goto leave; /* Skip unsigned attributes. */
}
if (ascii_strcasecmp (fields[1], "s"))
{
err = gpg_error (GPG_ERR_SYNTAX);
log_error ("invalid attribute specification '%s': %s\n",
attrstr, "type is not 's' or 'u'");
goto leave;
}
/* Check that the OID is valid. */
err = ksba_oid_from_str (fields[0], &der, &derlen);
if (err)
{
log_error ("invalid attribute specification '%s': %s\n",
attrstr, gpg_strerror (err));
goto leave;
}
xfree (der);
der = NULL;
if (strchr (fields[2], '/'))
{
/* FIXME: read from file. */
}
else /* Directly given in hex. */
{
for (i=0, s = fields[2]; hexdigitp (s); s++, i++)
;
if (*s || !i || (i&1))
{
log_error ("invalid attribute specification '%s': %s\n",
attrstr, "invalid hex encoding of the data");
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
der = xtrystrdup (fields[2]);
if (!der)
{
err = gpg_error_from_syserror ();
log_error ("malloc failed: %s\n", gpg_strerror (err));
goto leave;
}
for (s=fields[2], derlen=0; s[0] && s[1]; s += 2)
der[derlen++] = xtoi_2 (s);
}
/* Store the data in the CMS object for all signers. */
err = ksba_cms_add_attribute (cms, -1, fields[0], 0, der, derlen);
if (err)
{
log_error ("invalid attribute specification '%s': %s\n",
attrstr, gpg_strerror (err));
goto leave;
}
leave:
xfree (der);
xfree (fields);
return err;
}
#endif /*ksba >= 1.4.0 */
/* Perform a sign operation.
Sign the data received on DATA-FD in embedded mode or in detached
mode when DETACHED is true. Write the signature to OUT_FP. The
keys used to sign are taken from SIGNERLIST or the default one will
be used if the value of this argument is NULL. */
int
gpgsm_sign (ctrl_t ctrl, certlist_t signerlist,
int data_fd, int detached, estream_t out_fp)
{
int i, rc;
gpg_error_t err;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh = NULL;
gcry_md_hd_t data_md = NULL;
int signer;
const char *algoid;
int algo;
ksba_isotime_t signed_at;
certlist_t cl;
int release_signerlist = 0;
audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN);
kh = keydb_new ();
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!gnupg_rng_is_compliant (opt.compliance))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"random-compliance", rc);
goto leave;
}
ctrl->pem_name = "SIGNED MESSAGE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
err = ksba_cms_new (&cms);
if (err)
{
rc = err;
goto leave;
}
err = ksba_cms_set_reader_writer (cms, NULL, writer);
if (err)
{
log_debug ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* We are going to create signed data with data as encap. content.
* In authenticode mode we use spcIndirectDataContext instead. */
err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA);
if (!err)
err = ksba_cms_set_content_type
(cms, 1,
#if KSBA_VERSION_NUMBER >= 0x010400 && 0
opt.authenticode? KSBA_CT_SPC_IND_DATA_CTX :
#endif
KSBA_CT_DATA
);
if (err)
{
log_debug ("ksba_cms_set_content_type failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
/* If no list of signers is given, use the default certificate. */
if (!signerlist)
{
ksba_cert_t cert = get_default_signer (ctrl);
if (!cert)
{
log_error ("no default signer found\n");
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (GPG_ERR_NO_SECKEY), NULL);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Although we don't check for ambiguous specification we will
check that the signer's certificate is usable and valid. */
rc = gpgsm_cert_use_sign_p (cert, 0);
if (!rc)
rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL);
if (rc)
{
char *tmpfpr;
tmpfpr = gpgsm_get_fingerprint_hexstring (cert, 0);
gpgsm_status2 (ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), tmpfpr, NULL);
xfree (tmpfpr);
goto leave;
}
/* That one is fine - create signerlist. */
signerlist = xtrycalloc (1, sizeof *signerlist);
if (!signerlist)
{
rc = out_of_core ();
ksba_cert_release (cert);
goto leave;
}
signerlist->cert = cert;
release_signerlist = 1;
}
/* Figure out the hash algorithm to use. We do not want to use the
one for the certificate but if possible an OID for the plain
algorithm. */
if (opt.forced_digest_algo && opt.verbose)
log_info ("user requested hash algorithm %d\n", opt.forced_digest_algo);
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
{
const char *oid;
if (opt.forced_digest_algo)
{
oid = NULL;
cl->hash_algo = opt.forced_digest_algo;
}
else
{
oid = ksba_cert_get_digest_algo (cl->cert);
cl->hash_algo = oid ? gcry_md_map_name (oid) : 0;
}
switch (cl->hash_algo)
{
case GCRY_MD_SHA1: oid = "1.3.14.3.2.26"; break;
case GCRY_MD_RMD160: oid = "1.3.36.3.2.1"; break;
case GCRY_MD_SHA224: oid = "2.16.840.1.101.3.4.2.4"; break;
case GCRY_MD_SHA256: oid = "2.16.840.1.101.3.4.2.1"; break;
case GCRY_MD_SHA384: oid = "2.16.840.1.101.3.4.2.2"; break;
case GCRY_MD_SHA512: oid = "2.16.840.1.101.3.4.2.3"; break;
/* case GCRY_MD_WHIRLPOOL: oid = "No OID yet"; break; */
case GCRY_MD_MD5: /* We don't want to use MD5. */
case 0: /* No algorithm found in cert. */
default: /* Other algorithms. */
log_info (_("hash algorithm %d (%s) for signer %d not supported;"
" using %s\n"),
cl->hash_algo, oid? oid: "?", i,
gcry_md_algo_name (GCRY_MD_SHA1));
cl->hash_algo = GCRY_MD_SHA1;
oid = "1.3.14.3.2.26";
break;
}
cl->hash_algo_oid = oid;
/* Check compliance. */
if (! gnupg_digest_is_allowed (opt.compliance, 1, cl->hash_algo))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (cl->hash_algo),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
{
unsigned int nbits;
int pk_algo = gpgsm_get_key_algo_info (cl->cert, &nbits);
if (! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING, pk_algo,
NULL, nbits, NULL))
{
char kidstr[10+1];
snprintf (kidstr, sizeof kidstr, "0x%08lX",
gpgsm_get_short_fingerprint (cl->cert, NULL));
log_error (_("key %s may not be used for signing in %s mode\n"),
kidstr,
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
}
}
if (opt.verbose)
{
for (i=0, cl=signerlist; cl; cl = cl->next, i++)
log_info (_("hash algorithm used for signer %d: %s (%s)\n"),
i, gcry_md_algo_name (cl->hash_algo), cl->hash_algo_oid);
}
/* Gather certificates of signers and store them in the CMS object. */
for (cl=signerlist; cl; cl = cl->next)
{
rc = gpgsm_cert_use_sign_p (cl->cert, 0);
if (rc)
goto leave;
err = ksba_cms_add_signer (cms, cl->cert);
if (err)
{
log_error ("ksba_cms_add_signer failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
rc = add_certificate_list (ctrl, cms, cl->cert);
if (rc)
{
log_error ("failed to store list of certificates: %s\n",
gpg_strerror(rc));
goto leave;
}
/* Set the hash algorithm we are going to use */
err = ksba_cms_add_digest_algo (cms, cl->hash_algo_oid);
if (err)
{
log_debug ("ksba_cms_add_digest_algo failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Check whether one of the certificates is qualified. Note that we
already validated the certificate and thus the user data stored
flag must be available. */
if (!opt.no_chain_validation)
{
for (cl=signerlist; cl; cl = cl->next)
{
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cl->cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (err || !buflen)
{
log_error (_("checking for qualified certificate failed: %s\n"),
gpg_strerror (err));
rc = err;
goto leave;
}
if (*buffer)
err = gpgsm_qualified_consent (ctrl, cl->cert);
else
err = gpgsm_not_qualified_warning (ctrl, cl->cert);
if (err)
{
rc = err;
goto leave;
}
}
}
/* Prepare hashing (actually we are figuring out what we have set
above). */
rc = gcry_md_open (&data_md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (data_md, "sign.data");
for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++)
{
algo = gcry_md_map_name (algoid);
if (!algo)
{
log_error ("unknown hash algorithm '%s'\n", algoid? algoid:"?");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
gcry_md_enable (data_md, algo);
audit_log_i (ctrl->audit, AUDIT_DATA_HASH_ALGO, algo);
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
if (detached)
{ /* We hash the data right now so that we can store the message
digest. ksba_cms_build() takes this as an flag that detached
data is expected. */
unsigned char *digest;
size_t digest_len;
if (!hash_data (data_fd, data_md))
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer, digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
gnupg_get_isotime (signed_at);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
err = ksba_cms_set_signing_time (cms, signer, signed_at);
if (err)
{
log_error ("ksba_cms_set_signing_time failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* We can add signed attributes only when build against libksba 1.4. */
#if KSBA_VERSION_NUMBER >= 0x010400 && 0 /* 1.4.0 */
{
strlist_t sl;
for (sl = opt.attributes; sl; sl = sl->next)
if ((err = add_signed_attribute (cms, sl->d)))
goto leave;
}
#else
log_info ("Note: option --attribute is ignored by this version\n");
#endif /*ksba >= 1.4.0 */
/* We need to write at least a minimal list of our capabilities to
try to convince some MUAs to use 3DES and not the crippled
RC2. Our list is:
aes128-CBC
des-EDE3-CBC
*/
err = ksba_cms_add_smime_capability (cms, "2.16.840.1.101.3.4.1.2", NULL, 0);
if (!err)
err = ksba_cms_add_smime_capability (cms, "1.2.840.113549.3.7", NULL, 0);
if (err)
{
log_error ("ksba_cms_add_smime_capability failed: %s\n",
gpg_strerror (err));
goto leave;
}
/* Main building loop. */
do
{
err = ksba_cms_build (cms, &stopreason);
if (err)
{
log_debug ("ksba_cms_build failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
{
/* Hash the data and store the message digest. */
unsigned char *digest;
size_t digest_len;
assert (!detached);
rc = hash_and_copy_data (data_fd, data_md, writer);
if (rc)
goto leave;
audit_log (ctrl->audit, AUDIT_GOT_DATA);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
digest = gcry_md_read (data_md, cl->hash_algo);
digest_len = gcry_md_get_algo_dlen (cl->hash_algo);
if ( !digest || !digest_len )
{
log_error ("problem getting the hash of the data\n");
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = ksba_cms_set_message_digest (cms, signer,
digest, digest_len);
if (err)
{
log_error ("ksba_cms_set_message_digest failed: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
else if (stopreason == KSBA_SR_NEED_SIG)
{
/* Compute the signature for all signers. */
gcry_md_hd_t md;
rc = gcry_md_open (&md, 0, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (md, "sign.attr");
ksba_cms_set_hash_function (cms, HASH_FNC, md);
for (cl=signerlist,signer=0; cl; cl = cl->next, signer++)
{
unsigned char *sigval = NULL;
char *buf, *fpr;
audit_log_i (ctrl->audit, AUDIT_NEW_SIG, signer);
if (signer)
gcry_md_reset (md);
{
certlist_t cl_tmp;
for (cl_tmp=signerlist; cl_tmp; cl_tmp = cl_tmp->next)
{
gcry_md_enable (md, cl_tmp->hash_algo);
audit_log_i (ctrl->audit, AUDIT_ATTR_HASH_ALGO,
cl_tmp->hash_algo);
}
}
rc = ksba_cms_hash_signed_attrs (cms, signer);
if (rc)
{
log_debug ("hashing signed attrs failed: %s\n",
gpg_strerror (rc));
gcry_md_close (md);
goto leave;
}
rc = gpgsm_create_cms_signature (ctrl, cl->cert,
md, cl->hash_algo, &sigval);
if (rc)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, rc);
gcry_md_close (md);
goto leave;
}
err = ksba_cms_set_sig_val (cms, signer, sigval);
xfree (sigval);
if (err)
{
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, err);
log_error ("failed to store the signature: %s\n",
gpg_strerror (err));
rc = err;
gcry_md_close (md);
goto leave;
}
/* write a status message */
fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1);
if (!fpr)
{
rc = gpg_error (GPG_ERR_ENOMEM);
gcry_md_close (md);
goto leave;
}
rc = 0;
{
int pkalgo = gpgsm_get_key_algo_info (cl->cert, NULL);
buf = xtryasprintf ("%c %d %d 00 %s %s",
detached? 'D':'S',
pkalgo,
cl->hash_algo,
signed_at,
fpr);
if (!buf)
rc = gpg_error_from_syserror ();
}
xfree (fpr);
if (rc)
{
gcry_md_close (md);
goto leave;
}
gpgsm_status (ctrl, STATUS_SIG_CREATED, buf);
xfree (buf);
audit_log_cert (ctrl->audit, AUDIT_SIGNED_BY, cl->cert, 0);
}
gcry_md_close (md);
}
}
while (stopreason != KSBA_SR_READY);
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SIGNING_DONE);
log_info ("signature created\n");
leave:
if (rc)
log_error ("error creating signature: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc) );
if (release_signerlist)
gpgsm_release_certlist (signerlist);
ksba_cms_release (cms);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
gcry_md_close (data_md);
return rc;
}
diff --git a/tests/openpgp/README b/tests/openpgp/README
index 94a00fa40..aa99acceb 100644
--- a/tests/openpgp/README
+++ b/tests/openpgp/README
@@ -1,254 +1,254 @@
# Emacs, this is an -*- org -*- file.
* How to run the test suite
** tldr: How to run all tests fast.
obj $ make check-all TESTFLAGS=--parallel
You can use --parallel=N to request N parallel jobs. Hint: Tuck
TESTFLAGS=--parallel in your environment.
** Running individual test suites or tests
From your build directory, run
obj $ make -C tests/openpgp check
to run all tests or
obj $ make -C tests/openpgp check TESTS=your-test.scm
to run a specific test (or any number of tests separated by spaces).
If you want to debug a test, add verbose=1 to see messages printed by
spawned programs to their standard error stream, verbose=2 to see what
programs are executed, or verbose=3 to see even more program output
and exit codes.
** Inspecting the test environment
To inspect the environment in which tests are running, or to quickly
create keys for debugging or testing, you can start a shell. There is
-one test that doese just that:
+one test that does just that:
obj $ make -C tests/openpgp check TESTS=shell.scm
PASS: tests/openpgp/setup.scm
Load legacy test environment? [Y/n] y
Drop 'batch' from gpg.conf? [Y/n] y
Enjoy your test environment. Type 'exit' to exit it, it will be cleaned up after you.
... $ gpg -k Alfa
gpg: NOTE: THIS IS A DEVELOPMENT VERSION!
gpg: It is only intended for test purposes and should NOT be
gpg: used in a production environment or with production keys!
gpg: /tmp/gpgscm-20170809T144032-run-tests-PFfybw/trustdb.gpg: trustdb created
pub dsa1024 1999-03-08 [SCA]
A0FF4590BB6122EDEF6E3C542D727CC768697734
uid [ unknown] Alfa Test (demo key) <alfa@example.net>
uid [ unknown] Alpha Test (demo key) <alpha@example.net>
uid [ unknown] Alice (demo key)
sub elg1024 1999-03-08 [E]
PATH is adjusted so that you will use the tools from the build tree.
Note that the directory is removed when you exit the shell.
** Passing options to the test driver
You can set TESTFLAGS to pass flags to 'run-tests.scm'. For example,
to speed up the test suite when bisecting, do
obj $ make -C tests/openpgp check TESTFLAGS=--parallel
See below for the arguments supported by the driver.
** Calling the test driver directly
This is a bit tricky because one needs to manually set some
environment variables. We should make that easier. See discussion
below. From your build directory, do:
obj $ srcdir=<path to>/tests/openpgp \
GPGSCM_PATH=<path to>/tests/gpgscm:<path to>/tests/openpgp \
$(pwd)/tests/gpgscm/gpgscm [gpgscm args] \
run-tests.scm [test suite runner args]
*** Arguments supported by the test suite runner
The test suite runner supports two modes of operation, '--sequential'
and '--parallel'. By default the tests are run in sequential order,
each one in a clean environment.
You can specify the tests to run as positional arguments relative to
srcdir (e.g. just 'version.scm'). Note that you do not have to
specify setup.scm and finish.scm, they are executed implicitly.
The test suite runner can be executed in any location that the current
user can write to. It will create temporary files and directories,
but will in general clean up all of them.
*** Discussion of the various environment variables
**** srcdir
Must be set to the source of the openpgp test suite. Used to locate
data files.
**** GPGSCM_PATH
Used to locate the Scheme library as well as code used by the test
suite.
**** BIN_PREFIX
The test suite does not hardcode any paths to tools. If set it is
used to locate the tools to test, otherwise the test suite assumes to
be run from the build directory.
**** GPG_PRESET_PASSPHRASE
This tool is not installed by 'make install', hence we need to
explicitly override its position. In fact, the location of any tool
used by the test suite can be overridden this way. See defs.scm.
**** argv[0]
run-tests.scm depends on being able to re-exec gpgscm. It uses
argv[0] for that. Therefore you must use an absolute path to invoke
gpgscm.
* How to write tests
gpgscm provides a number of functions to aid you in writing tests, as
well as bindings to process management abstractions provided by GnuPG.
For the Scheme environment provided by TinySCHEME, see the TinySCHEME
manual that is included in tests/gpgscm/Manual.txt.
For a quick start, please have a look at various tests that are
already implemented, e.g. 'encrypt.scm'.
** The test framework
The functions info, error, and skip display their first argument and
flush the output buffers. error and skip will also terminate the
process, signaling that the test failed or should be skipped.
(for-each-p msg proc list) will display msg, and call proc with each
element of list while displaying the progress appropriately.
for-each-p' is similar, but accepts another callback before the 'list'
argument to format each item. for-each-p can be safely nested, and
the inner progress indicator will be abbreviated using '.'.
** Debugging tests
Say you are working on a new test called 'your-test.scm', you can run
it on its own using
obj $ make -C tests/openpgp check TESTS=your-test.scm
but something isn't working as expected. There are several little
gadgets that might help. The first one is 'trace', a function that
prints the value given to it and evaluates to it. E.g.
(trace (+ 2 3))
prints '5' and evaluates to 5. Also, there is an 'assert' macro that
aborts the execution if its argument does not evaluate to a trueish
value. Feel free to express invariants with it.
You can also get an interactive repl by dropping
(interactive-repl (current-environment))
anywhere you like. Or, if you want to examine the environment from an
operating system shell, use
(interactive-shell)
** Interfacing with gpg
defs.scm defines several convenience functions. Say you want to parse
the colon output from gpg, there is gpg-with-colons that splits the
result at newlines and colons, so you can use the result like this:
(define (fpr some-key)
(list-ref (assoc "fpr" (gpg-with-colons
`(--with-fingerprint
--list-secret-keys ,some-key)))
9))
Or if you want to count all non-revoked uids for a given key, do
(define (count-uids-of-secret-key some-key)
(length (filter (lambda (x) (and (string=? "uid" (car x))
(string=? "u" (cadr x))))
(gpg-with-colons
`(--with-fingerprint
--list-secret-keys ,some-key)))))
** Temporary files
(lettmp <bindings> <body>) will create and delete temporary files that
you can use in <body>. (with-temporary-working-directory <body>) will
create a temporary director, change to that, and clean it up after
executing <body>).
make-temporary-file will create a temporary file. You can optionally
provide an argument to that function that will serve as tag so you can
distinguish the files for debugging. remove-temporary-file will
delete a file created using make-temporary-file.
** Monadic transformer and pipe support
Tests often perform sequential transformations on files, or connect
processes using pipes. To aid you in this, the test framework
provides two monadic data structures.
(Currently, the implementation mashes the 'bind' operation together
with the application of the monad. Also, there is no 'return'
operation. I guess all of that could be implemented on top of
call/cc, but it isn't at the moment.)
*** pipe
The pipe monad constructs pipe lines. It consists of a function
pipe:do that binds the functions together and manages the execution of
the child processes, a family of functions that act as sources, a
function to spawn processes, and a family of functions acting as
sinks.
Sources are pipe:open, pipe:defer, pipe:echo. To spawn a process use
pipe:spawn, or the convenience function pipe:gpg. To sink the data
use pipe:splice, or pipe:write-to.
Example:
(pipe:do
(pipe:echo "3\n1\n2\n")
(pipe:spawn '("/usr/bin/sort"))
(pipe:write-to "sorted" (logior O_WRONLY O_CREAT) #o600))
Caveats: Due to the single-threaded nature of gpgscm you cannot use
both a source and sink that is implemented in Scheme. pipe:defer and
pipe:echo are executing in gpgscm, and so does pipe:splice.
*** tr
The transformer monad describes sequential file transformations.
There is one source function, tr:open. To describe a transformation
using some process, use tr:spawn, tr:gpg, or tr:pipe-do. There are
several sinks, although sink is not quite the right term, because the
data is not consumed, and hence one can use them at any position. The
"sinks" are tr:write-to, tr:call-with-content, tr:assert-identity, and
tr:assert-weak-identity.
A somewhat contrived example demonstrating many functions is:
(tr:do
(tr:pipe-do
(pipe:echo "3\n1\n2\n")
(pipe:spawn '("/usr/bin/sort")))
(tr:write-to "reference")
(tr:call-with-content
(lambda (c)
(echo "currently, c contains" (string-length c) "bytes")))
(tr:spawn "" '("/usr/bin/gcc" -x c "-E" -o **out** **in**))
(tr:pipe-do
(pipe:spawn '("/bin/grep" -v "#")))
(tr:assert-identity "reference"))
Caveats: As a convenience, gpgscm allows one to specify command line
arguments as Scheme symbols. Scheme symbols, however, are
case-insensitive, and get converted to lower case. Therefore, the -E
argument must be given as a string in the example above. Similarly,
you need to quote numerical values.
** Process management
If you just need to execute a single command, there is (call-with-fds
cmdline infd outfd errfd) which executes cmdline with the given file
descriptors bound to it, and waits for its completion returning the
status code. There is (call cmdline) which is similar, but calls the
command with a closed stdin, connecting stdout and stderr to stderr if
gpgscm is executed with --verbose. (call-check cmdline) raises an
exception if the command does not return 0.
(call-popen cmdline input) calls a command, writes input to its stdin,
and returns any output from stdout, or raises an exception containing
stderr on failure.
* Sample messages
diff --git a/tests/sm-verify b/tests/sm-verify
index b06dc16a4..fa0932300 100644
--- a/tests/sm-verify
+++ b/tests/sm-verify
@@ -1,114 +1,114 @@
# sm-verify
#
# Verify a few distributed signatures.
# Requirements:
#
srcdir = getenv srcdir
# Check an opaque signature
sig = openfile $srcdir/text-1.osig.pem
out = createfile msg.unsig
pipeserver $GPGSM
send INPUT FD=$sig
expect-ok
send OUTPUT FD=$out
expect-ok
badsig = count-status BADSIG
goodsig = count-status GOODSIG
trusted = count-status TRUST_FULLY
send VERIFY
expect-ok
echo badsig=$badsig goodsig=$goodsig trusted=$trusted
fail-if $badsig
fail-if !$goodsig
fail-if !$trusted
send BYE
expect-ok
sig =
out =
cmpfiles $srcdir/text-1.txt msg.unsig
fail-if !$?
# Check a detached signature.
sig = openfile $srcdir/text-1.dsig.pem
plain = openfile $srcdir/text-1.txt
pipeserver $GPGSM
send INPUT FD=$sig
expect-ok
send MESSAGE FD=$plain
expect-ok
badsig = count-status BADSIG
goodsig = count-status GOODSIG
trusted = count-status TRUST_FULLY
send VERIFY
expect-ok
echo badsig=$badsig goodsig=$goodsig trusted=$trusted
fail-if $badsig
fail-if !$goodsig
fail-if !$trusted
send BYE
expect-ok
# Check a tampered opaque message
sig = openfile $srcdir/text-1.osig-bad.pem
out = createfile msg.unsig
pipeserver $GPGSM
send INPUT FD=$sig
expect-ok
send OUTPUT FD=$out
expect-ok
badsig = count-status BADSIG
goodsig = count-status GOODSIG
trusted = count-status TRUST_FULLY
send VERIFY
expect-ok
echo badsig=$badsig goodsig=$goodsig trusted=$trusted
fail-if $goodsig
fail-if !$badsig
fail-if $trusted
send BYE
expect-ok
# Check another opaque signature but without asking for the output.
sig = openfile $srcdir/text-2.osig.pem
pipeserver $GPGSM
send INPUT FD=$sig
expect-ok
badsig = count-status BADSIG
goodsig = count-status GOODSIG
trusted = count-status TRUST_FULLY
send VERIFY
expect-ok
echo badsig=$badsig goodsig=$goodsig trusted=$trusted
fail-if $badsig
fail-if !$goodsig
fail-if !$trusted
send BYE
expect-ok
-# We als have tampered version.
+# We also have tampered version.
sig = openfile $srcdir/text-2.osig-bad.pem
pipeserver $GPGSM
send INPUT FD=$sig
expect-ok
badsig = count-status BADSIG
goodsig = count-status GOODSIG
trusted = count-status TRUST_FULLY
send VERIFY
expect-ok
echo badsig=$badsig goodsig=$goodsig trusted=$trusted
fail-if $goodsig
fail-if !$badsig
fail-if $trusted
send BYE
expect-ok
quit
diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c
index 1737630a2..62c1d1b2f 100644
--- a/tools/card-call-scd.c
+++ b/tools/card-call-scd.c
@@ -1,1644 +1,1644 @@
/* card-call-scd.c - IPC calls to scdaemon.
* Copyright (C) 2019, 2020 g10 Code GmbH
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
#define START_AGENT_NO_STARTUP_CMDS 1
#define START_AGENT_SUPPRESS_ERRORS 2
struct default_inq_parm_s
{
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct card_cardlist_parm_s
{
gpg_error_t error;
int with_apps;
strlist_t list;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
/*
* File local variables
*/
/* The established context to the agent. Note that all calls to
* scdaemon are routed via the agent and thus we only need to care
* about the IPC with the agent. */
static assuan_context_t agent_ctx;
/*
* Local prototypes
*/
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* Release the card info structure INFO. */
void
release_card_info (card_info_t info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->cardtype); info->cardtype = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->dispserialno); info->dispserialno = NULL;
xfree (info->apptypestr); info->apptypestr = NULL;
info->apptype = APP_TYPE_NONE;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
while (info->kinfo)
{
key_info_t kinfo = info->kinfo->next;
xfree (info->kinfo);
info->kinfo = kinfo;
}
info->chvusage[0] = info->chvusage[1] = 0;
}
/* Map an application type string to an integer. */
static app_type_t
map_apptypestr (const char *string)
{
app_type_t result;
if (!string)
result = APP_TYPE_NONE;
else if (!ascii_strcasecmp (string, "OPENPGP"))
result = APP_TYPE_OPENPGP;
else if (!ascii_strcasecmp (string, "NKS"))
result = APP_TYPE_NKS;
else if (!ascii_strcasecmp (string, "DINSIG"))
result = APP_TYPE_DINSIG;
else if (!ascii_strcasecmp (string, "P15"))
result = APP_TYPE_P15;
else if (!ascii_strcasecmp (string, "GELDKARTE"))
result = APP_TYPE_GELDKARTE;
else if (!ascii_strcasecmp (string, "SC-HSM"))
result = APP_TYPE_SC_HSM;
else if (!ascii_strcasecmp (string, "PIV"))
result = APP_TYPE_PIV;
else
result = APP_TYPE_UNKNOWN;
return result;
}
/* Return a string representation of the application type. */
const char *
app_type_string (app_type_t app_type)
{
const char *result = "?";
switch (app_type)
{
case APP_TYPE_NONE: result = "None"; break;
case APP_TYPE_OPENPGP: result = "OpenPGP"; break;
case APP_TYPE_NKS: result = "NetKey"; break;
case APP_TYPE_DINSIG: result = "DINSIG"; break;
case APP_TYPE_P15: result = "P15"; break;
case APP_TYPE_GELDKARTE: result = "Geldkarte"; break;
case APP_TYPE_SC_HSM: result = "SC-HSM"; break;
case APP_TYPE_PIV: result = "PIV"; break;
case APP_TYPE_UNKNOWN: result = "Unknown"; break;
}
return result;
}
/* If RC is not 0, write an appropriate status message. */
static gpg_error_t
status_sc_op_failure (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
gnupg_status_printf (STATUS_SC_OP_FAILURE, "2");
break;
default:
gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL);
break;
}
return err;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
(void)parm;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
/* err = gpg_proxy_pinentry_notify (parm->ctrl, line); */
/* if (err) */
/* log_error (_("failed to proxy %s inquiry to client\n"), */
/* "PINENTRY_LAUNCHED"); */
/* We do not pass errors to avoid breaking other code. */
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
gpg_error_t err;
char *serverversion;
const char *myversion = strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (!opt.quiet)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s",
warn);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
/* Try to connect to the agent via socket or fork it off and work by
* pipes. Handle the server's initial greeting. */
static gpg_error_t
start_agent (unsigned int flags)
{
gpg_error_t err;
int started = 0;
if (agent_ctx)
err = 0;
else
{
started = 1;
err = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart, opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!err
&& !(err = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
}
}
if (started && !err && !(flags & START_AGENT_NO_STARTUP_CMDS))
{
/* Request the serial number of the card for an early test. */
struct card_info_s info;
memset (&info, 0, sizeof info);
if (!(flags & START_AGENT_SUPPRESS_ERRORS))
err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!err)
err = assuan_transact (agent_ctx, "SCD SERIALNO --all",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (err && !(flags & START_AGENT_SUPPRESS_ERRORS))
{
switch (gpg_err_code (err))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */
break;
case GPG_ERR_OBJ_TERM_STATE:
/* Card is in termination state. */
gnupg_status_printf (STATUS_CARDCTRL, "7");
break;
default:
gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */
break;
}
}
if (!err && info.serialno)
gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno);
release_card_info (&info);
}
return err;
}
/* Return a new malloced string by unescaping the string S. Escaping
* is percent escaping and '+'/space mapping. A binary nul will
* silently be replaced by a 0xFF. Function returns NULL to indicate
* an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
* allocated string. We make sure that only hex characters are
* returned. Returns NULL on error. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a
* RESET command is send to scd. With HEXAPDU being the string
* "undefined" the command "SERIALNO undefined" is send to scd. If
* R_DATA is not NULL the data is without the status code is stored
* there. Caller must release it. */
gpg_error_t
scd_apdu (const char *hexapdu, unsigned int *r_sw,
unsigned char **r_data, size_t *r_datalen)
{
gpg_error_t err;
if (r_data)
*r_data = NULL;
if (r_datalen)
*r_datalen = 0;
err = start_agent (START_AGENT_NO_STARTUP_CMDS);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
if (r_sw)
*r_sw = buf16_to_uint (data+datalen-2);
if (r_data && r_datalen)
{
*r_data = data;
*r_datalen = datalen - 2;
data = NULL;
}
}
xfree (data);
}
}
return err;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
/* FIXME: Should we use has_leading_keyword? */
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Make the card with SERIALNO the current one. */
gpg_error_t
scd_switchcard (const char *serialno)
{
int err;
char line[ASSUAN_LINELENGTH];
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHCARD -- %s", serialno);
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
/* Make the app APPNAME the one on the card. */
gpg_error_t
scd_switchapp (const char *appname)
{
int err;
char line[ASSUAN_LINELENGTH];
if (appname && !*appname)
appname = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s",
appname? " ":"", appname? appname:"");
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
/* For historical reasons OpenPGP cards simply use the numbers 1 to 3
* for the <keyref>. Other cards and future versions of
* scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1"
* instead of "1". This is a helper to cope with that. */
static const char *
parse_keyref_helper (const char *string)
{
if (*string == '1' && spacep (string+1))
return "OPENPGP.1";
else if (*string == '2' && spacep (string+1))
return "OPENPGP.2";
else if (*string == '3' && spacep (string+1))
return "OPENPGP.3";
else
return string;
}
/* Create a new key info object with KEYREF. All fields but the
* keyref are zeroed out. Never returns NULL. The created object is
* appended to the list at INFO. */
static key_info_t
create_kinfo (card_info_t info, const char *keyref)
{
key_info_t kinfo, ki;
kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref));
strcpy (kinfo->keyref, keyref);
if (!info->kinfo)
info->kinfo = kinfo;
else
{
for (ki=info->kinfo; ki->next; ki = ki->next)
;
ki->next = kinfo;
}
return kinfo;
}
/* The status callback to handle the LEARN and GETATTR commands. */
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL; /* In case we need a copy. */
char *pline;
key_info_t kinfo;
const char *keyref;
int i;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
switch (keywordlen)
{
case 3:
if (!memcmp (keyword, "KDF", 3))
{
parm->kdf_do_enabled = 1;
}
break;
case 5:
if (!memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
break;
case 6:
if (!memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (!memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (!memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,
sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,
sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,
sizeof parm->cafpr3);
}
break;
case 7:
if (!memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptypestr);
parm->apptypestr = unescape_status_string (line);
parm->apptype = map_apptypestr (parm->apptypestr);
}
else if (!memcmp (keyword, "KEY-FPR", keywordlen))
{
/* The format of such a line is:
* KEY-FPR <keyref> <fingerprintinhex>
*/
const char *fpr;
line_buffer = pline = xstrdup (line);
keyref = parse_keyref_helper (pline);
while (*pline && !spacep (pline))
pline++;
if (*pline)
*pline++ = 0; /* Terminate keyref. */
while (spacep (pline)) /* Skip to the fingerprint. */
pline++;
fpr = pline;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear the fpr. */
memset (kinfo->fpr, 0, sizeof kinfo->fpr);
/* Set or update or the fingerprint. */
kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr);
}
break;
case 8:
if (!memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& xtoi_2 (parm->serialno+12) >= 2 );
}
else if (!memcmp (keyword, "CARDTYPE", keywordlen))
{
xfree (parm->cardtype);
parm->cardtype = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (!memcmp (keyword, "KEY-ATTR", keywordlen))
{
char keyrefbuf[20];
int keyno, algo, n;
const char *curve;
unsigned int nbits;
/* To prepare for future changes we allow for a full OpenPGP
* keyref here. */
if (!ascii_strncasecmp (line, "OPENPGP.", 8))
line += 8;
/* Note that KEY-ATTR returns OpenPGP algorithm numbers but
* we want to use the Gcrypt numbers here. A compatible
- * change would be to add another paramater along with a
+ * change would be to add another parameter along with a
* magic algo number to indicate that. */
algo = PUBKEY_ALGO_RSA;
keyno = n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
algo = map_openpgp_pk_to_gcry (algo);
if (keyno < 1 || keyno > 3)
; /* Out of range - ignore. */
else
{
snprintf (keyrefbuf, sizeof keyrefbuf, "OPENPGP.%d", keyno);
keyref = keyrefbuf;
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* No: new entry. */
kinfo = create_kinfo (parm, keyref);
/* Although we could use the the value at %n directly as
* keyalgo string, we want to use the standard
* keyalgo_string function and thus we reconstruct it
* here to make sure the displayed form of the curve
* names is used. */
nbits = 0;
curve = NULL;
if (algo == GCRY_PK_ECDH || algo == GCRY_PK_ECDSA
|| algo == GCRY_PK_EDDSA || algo == GCRY_PK_ECC)
{
curve = openpgp_is_curve_supported (line + n, NULL, NULL);
}
else /* For rsa we see here for example "rsa2048". */
{
if (line[n] && line[n+1] && line[n+2])
nbits = strtoul (line+n+3, NULL, 10);
}
kinfo->keyalgo = get_keyalgo_string (algo, nbits, curve);
kinfo->keyalgo_id = algo;
}
}
break;
case 9:
if (!memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (!memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-USAGE", keywordlen))
{
unsigned int byte1, byte2;
byte1 = byte2 = 0;
sscanf (line, "%x %x", &byte1, &byte2);
parm->chvusage[0] = byte1;
parm->chvusage[1] = byte2;
}
break;
case 10:
if (!memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (!memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (!memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
while (spacep (p))
p++;
if (!buf)
;
else if (parm->apptype == APP_TYPE_OPENPGP)
{
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
}
else if (parm->apptype == APP_TYPE_PIV)
{
for (i=0; *p && i < DIM (parm->chvinfo); i++)
{
parm->chvinfo[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
}
xfree (buf);
}
else if (!memcmp (keyword, "APPVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->appversion = val;
}
break;
case 11:
if (!memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*/
char *fields[4];
int nfields;
const char *hexgrp, *usage;
time_t keytime;
line_buffer = pline = xstrdup (line);
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - invalid status line. */
hexgrp = fields[0];
keyref = fields[1];
if (nfields > 2)
usage = fields[2];
else
usage = "";
if (nfields > 3)
keytime = parse_timestamp (fields[3], NULL);
else
keytime = 0;
/* Check whether we already have an item for the keyref. */
kinfo = find_kinfo (parm, keyref);
if (!kinfo) /* New entry. */
kinfo = create_kinfo (parm, keyref);
else /* Existing entry - clear grip and usage */
{
memset (kinfo->grip, 0, sizeof kinfo->grip);
kinfo->usage = 0;
}
/* Set or update the grip. Note that due to the
* calloc/memset an erroneous too short grip will be nul
* padded on the right. */
unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip);
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
/* Store the keytime. */
kinfo->created = keytime == (time_t)(-1)? 0 : (u32)keytime;
}
else if (!memcmp (keyword, "CARDVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->cardversion = val;
}
break;
case 12:
if (!memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
break;
case 13:
if (!memcmp (keyword, "$DISPSERIALNO", keywordlen))
{
xfree (parm->dispserialno);
parm->dispserialno = unescape_status_string (line);
}
break;
default:
/* Unknown. */
break;
}
leave:
xfree (line_buffer);
return 0;
}
/* Call the scdaemon to learn about a smartcard. This fills INFO
* with data from the card. */
gpg_error_t
scd_learn (card_info_t info)
{
gpg_error_t err;
struct default_inq_parm_s parm;
struct card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "SCD LEARN --force",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get some other key attributes. */
if (!err)
{
info->initialized = 1;
err = scd_getattr ("KEY-ATTR", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
err = scd_getattr ("$DISPSERIALNO", info);
if (gpg_err_code (err) == GPG_ERR_INV_NAME
|| gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION)
err = 0; /* Not implemented or GETATTR not supported. */
}
if (info == &dummyinfo)
release_card_info (info);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command. */
gpg_error_t
scd_getattr (const char *name, struct card_info_s *info)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (0);
if (err)
return err;
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
return err;
}
/* Send an setattr command to the SCdaemon. */
gpg_error_t
scd_setattr (const char *name,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
char *tmp;
char *line = NULL;
struct default_inq_parm_s parm;
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
tmp = strconcat ("SCD SETATTR ", name, " ", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
line = percent_data_escape (1, tmp, value, valuelen);
xfree (tmp);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 10 > ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
err = start_agent (0);
if (err )
goto leave;
memset (&parm, 0, sizeof parm);
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
leave:
xfree (line);
return status_sc_op_failure (err);
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
* assuan_transact takes care of flushing and writing the END
* command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
gpg_error_t err;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Send a WRITECERT command to the SCdaemon. */
gpg_error_t
scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
memset (&parms, 0, sizeof parms);
snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
err = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return status_sc_op_failure (err);
}
/* Send a WRITEKEY command to the agent (so that the agent can fetch
* the key to write). KEYGRIP is the hexified keygrip of the source
- * key which will be written to tye slot KEYREF. FORCE must be true
+ * key which will be written to the slot KEYREF. FORCE must be true
* to overwrite an existing key. */
gpg_error_t
scd_writekey (const char *keyref, int force, const char *keygrip)
{
gpg_error_t err;
struct default_inq_parm_s parm;
char line[ASSUAN_LINELENGTH];
memset (&parm, 0, sizeof parm);
err = start_agent (0);
if (err)
return err;
/* Note: We don't send the s/n but "-" because gpg-agent has
* currently no use for it. */
/* FIXME: For OpenPGP we should provide the creation time. */
snprintf (line, sizeof line, "KEYTOCARD%s %s - %s",
force? " --force":"", keygrip, keyref);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
return status_sc_op_failure (err);
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
if (createtime)
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
gnupg_status_printf (STATUS_PROGRESS, "%s", line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDAEMON. On success, creation time is stored back to CREATETIME. */
gpg_error_t
scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
if (createtime && *createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
algo? "--algo=":"",
algo? algo:"",
keyref);
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
return status_sc_op_failure (err);
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. If DEMAND is not NULL
* the reader with the a card of the serial number DEMAND is
* requested. */
gpg_error_t
scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO --all");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s --all", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
if (r_serialno)
*r_serialno = serialno;
else
xfree (serialno);
return 0;
}
/* Send a READCERT command to the SCdaemon. */
gpg_error_t
scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, sizeof line, "SCD READCERT %s", certidstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error_from_syserror ();
return 0;
}
/* Send a READKEY command to the SCdaemon. On success a new
* s-expression is stored at R_RESULT. */
gpg_error_t
scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
*r_result = NULL;
err = start_agent (0);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
NULL, NULL,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
err = gcry_sexp_new (r_result, buf, buflen, 0);
xfree (buf);
return err;
}
/* Callback function for card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1))
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
if (parm->with_apps)
{
/* Format of the stored string is the S/N, a space, and a
* space separated list of appnames. */
if (*s && (*s != ' ' || spacep (s+1) || !s[1]))
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else /* We assume the rest of the line is well formatted. */
add_to_strlist (&parm->list, line);
}
else
{
if (*s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
}
return 0;
}
/* Return the serial numbers of all cards currently inserted. */
gpg_error_t
scd_cardlist (strlist_t *result)
{
gpg_error_t err;
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, "SCD GETINFO card_list",
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return err;
}
/* Return the serial numbers and appnames of the current card or, with
* ALL given has true, of all cards currently inserted. */
gpg_error_t
scd_applist (strlist_t *result, int all)
{
gpg_error_t err;
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (START_AGENT_SUPPRESS_ERRORS);
if (err)
return err;
parm.with_apps = 1;
err = assuan_transact (agent_ctx,
all ? "SCD GETINFO all_active_apps"
/**/: "SCD GETINFO active_apps",
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return err;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
* CHVNO 1: Change the PIN
* 2: For v1 cards: Same as 1.
* For v2 cards: Reset the PIN using the Reset Code.
* 3: Change the admin PIN
* 101: Set a new PIN and reset the retry counter
* 102: For v1 cars: Same as 101.
* For v2 cards: Set a new Reset Code.
*/
gpg_error_t
scd_change_pin (const char *pinref, int reset_mode)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD PASSWD%s %s",
reset_mode? " --reset":"", pinref);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here. */
gpg_error_t
scd_checkpin (const char *serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
err = start_agent (0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno);
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return status_sc_op_failure (err);
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c
index fff669cc0..a723739f1 100644
--- a/tools/card-yubikey.c
+++ b/tools/card-yubikey.c
@@ -1,446 +1,446 @@
/* card-yubikey.c - Yubikey specific functions.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/ttyio.h"
#include "gpg-card.h"
/* Object to describe requested interface options. */
struct iface_s {
unsigned int usb:1;
unsigned int nfc:1;
};
/* Bit flags as used by the fields in struct ykapps_s. */
#define YKAPP_USB_SUPPORTED 0x01
#define YKAPP_USB_ENABLED 0x02
#define YKAPP_NFC_SUPPORTED 0x04
#define YKAPP_NFC_ENABLED 0x08
#define YKAPP_SELECTED 0x80 /* Selected by the command. */
/* An object to describe the applications on a Yubikey. Each field
* has 8 bits to hold the above flag values. */
struct ykapps_s {
unsigned int otp:8;
unsigned int u2f:8;
unsigned int opgp:8;
unsigned int piv:8;
unsigned int oath:8;
unsigned int fido2:8;
};
/* Helper to parse an unsigned integer config value consisting of bit
* flags. TAG select the config item and MASK is the mask ORed into
* the value for a set bit. The function modifies YK. */
static gpg_error_t
parse_ul_config_value (struct ykapps_s *yk,
const unsigned char *config, size_t configlen,
int tag, unsigned int mask)
{
const unsigned char *s;
size_t n;
unsigned long ul = 0;
int i;
s = find_tlv (config, configlen, tag, &n);
if (s && n)
{
if (n > sizeof ul)
{
log_error ("too large integer in Yubikey config tag %02x detected\n",
tag);
if (opt.verbose)
log_printhex (config, configlen, "config:");
return gpg_error (GPG_ERR_CARD);
}
for (i=0; i < n; i++)
{
ul <<=8;
ul |= s[i];
}
if (ul & 0x01)
yk->otp |= mask;
if (ul & 0x02)
yk->u2f |= mask;
if (ul & 0x08)
yk->opgp |= mask;
if (ul & 0x10)
yk->piv |= mask;
if (ul & 0x20)
yk->oath |= mask;
if (ul & 0x200)
yk->fido2 |= mask;
}
return 0;
}
/* Create an unsigned integer config value for TAG from the data in YK
* and store it the provided 4 byte buffer RESULT. If ENABLE is true
* the respective APP_SELECTED bit in YK sets the corresponding bit
* flags, it is is false that bit flag is cleared. IF APP_SELECTED is
* not set the bit flag is not changed. */
static void
set_ul_config_value (struct ykapps_s *yk,
unsigned int bitflag, int tag, unsigned int enable,
unsigned char *result)
{
unsigned long ul = 0;
/* First set the current values. */
if ((yk->otp & bitflag))
ul |= 0x01;
if ((yk->u2f & bitflag))
ul |= 0x02;
if ((yk->opgp & bitflag))
ul |= 0x08;
if ((yk->piv & bitflag))
ul |= 0x10;
if ((yk->oath & bitflag))
ul |= 0x20;
if ((yk->fido2 & bitflag))
ul |= 0x200;
/* Then enable or disable the bits according to the selection flag. */
if (enable)
{
if ((yk->otp & YKAPP_SELECTED))
ul |= 0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul |= 0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul |= 0x08;
if ((yk->piv & YKAPP_SELECTED))
ul |= 0x10;
if ((yk->oath & YKAPP_SELECTED))
ul |= 0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul |= 0x200;
}
else
{
if ((yk->otp & YKAPP_SELECTED))
ul &= ~0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul &= ~0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul &= ~0x08;
if ((yk->piv & YKAPP_SELECTED))
ul &= ~0x10;
if ((yk->oath & YKAPP_SELECTED))
ul &= ~0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul &= ~0x200;
}
/* Make sure that we do not disable the CCID transport. Without
* CCID we won't have any way to change the configuration again. We
* would instead need one of the other Yubikey tools to enable an
* application and thus its transport again. */
if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20)))
{
log_info ("Enabling PIV to have at least one CCID transport\n");
ul |= 0x10;
}
result[0] = tag;
result[1] = 2;
result[2] = ul >> 8;
result[3] = ul;
}
/* Print the info from YK. */
static void
yk_list (estream_t fp, struct ykapps_s *yk)
{
if (opt.interactive)
tty_fprintf (fp, ("Application USB NFC\n"
"-----------------------\n"));
tty_fprintf (fp, "OTP %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "U2F %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OPGP %s %s\n",
(yk->opgp & YKAPP_USB_SUPPORTED)?
(yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->opgp & YKAPP_NFC_SUPPORTED)?
(yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "PIV %s %s\n",
(yk->piv & YKAPP_USB_SUPPORTED)?
(yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->piv & YKAPP_NFC_SUPPORTED)?
(yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OATH %s %s\n",
(yk->oath & YKAPP_USB_SUPPORTED)?
(yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->oath & YKAPP_NFC_SUPPORTED)?
(yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "FIDO2 %s %s\n",
(yk->fido2 & YKAPP_USB_SUPPORTED)?
(yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->fido2 & YKAPP_NFC_SUPPORTED)?
(yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
}
/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */
static gpg_error_t
yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface,
const unsigned char *config, size_t configlen, int enable)
{
gpg_error_t err = 0;
unsigned char apdu[100];
unsigned int apdulen;
/* const unsigned char *s; */
/* size_t n; */
char *hexapdu = NULL;
apdulen = 0;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x1c; /* Write Config instruction. */
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */
apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */
/* The ykman tool has no way to set NFC and USB flags in one go.
* Reasoning about the Yubikey's firmware it seems plausible that
* combining should work. Let's try it here if the user called for
* setting both interfaces. */
if (iface->nfc)
{
set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen);
apdulen += 4;
}
if (iface->usb)
{
set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen);
apdulen += 4;
/* Yubikey's ykman copies parts of the config data when writing
* the config for USB. Below is a commented example on how that
* can be done. */
(void)config;
(void)configlen;
/* Copy the device flags. */
/* s = find_tlv (config, configlen, 0x08, &n); */
/* if (s && n) */
/* { */
/* s -= 2; */
/* n += 2; */
/* if (apdulen + n > sizeof apdu) */
/* { */
/* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */
/* goto leave; */
/* } */
/* memcpy (apdu+apdulen, s, n); */
/* apdulen += n; */
/* } */
}
if (iface->nfc || iface->usb)
{
if (apdulen + 2 > sizeof apdu)
{
err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
goto leave;
}
/* Disable the next two lines to let the card reboot. Not doing
* this is however more convenient for this tool because further
* commands don't end up with an error. It seems to be better
* that a "reset" command from gpg-card-tool is run at the
* user's discretion. */
/* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */
/* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */
/* Fixup the lngth bytes. */
apdu[4] = apdulen - 6 + 1;
apdu[5] = apdulen - 6;
hexapdu = bin2hex (apdu, apdulen, NULL);
if (!hexapdu)
err = gpg_error_from_syserror ();
else
err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL);
}
leave:
xfree (hexapdu);
return err;
}
/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc
* with the argumets given to the yubikey command. Note that ARGV has
- * no terminating NULL so that ARGC must be considred. FP is the
+ * no terminating NULL so that ARGC must be considered. FP is the
* stream to output information. This function must only be called on
* Yubikeys. */
gpg_error_t
yubikey_commands (card_info_t info, estream_t fp, int argc, char *argv[])
{
gpg_error_t err;
enum {ykLIST, ykENABLE, ykDISABLE } cmd;
struct iface_s iface = {0,0};
struct ykapps_s ykapps = {0};
unsigned char *config = NULL;
size_t configlen;
int i;
if (!argc)
return gpg_error (GPG_ERR_SYNTAX);
/* Parse command. */
if (!ascii_strcasecmp (argv[0], "list"))
cmd = ykLIST;
else if (!ascii_strcasecmp (argv[0], "enable"))
cmd = ykENABLE;
else if (!ascii_strcasecmp (argv[0], "disable"))
cmd = ykDISABLE;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_COMMAND);
goto leave;
}
if (info->cardversion < 0x050000 && cmd != ykLIST)
{
log_info ("Sub-command '%s' is only support by Yubikey-5 and later\n",
argv[0]);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Parse interface if needed. */
if (cmd == ykLIST)
iface.usb = iface.nfc = 1;
else if (argc < 2)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
else if (!ascii_strcasecmp (argv[1], "usb"))
iface.usb = 1;
else if (!ascii_strcasecmp (argv[1], "nfc"))
iface.nfc = 1;
else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*"))
iface.usb = iface.nfc = 1;
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Parse list of applications. */
for (i=2; i < argc; i++)
{
if (!ascii_strcasecmp (argv[i], "otp"))
ykapps.otp = 0x80;
else if (!ascii_strcasecmp (argv[i], "u2f"))
ykapps.u2f = 0x80;
else if (!ascii_strcasecmp (argv[i], "opgp")
||!ascii_strcasecmp (argv[i], "openpgp"))
ykapps.opgp = 0x80;
else if (!ascii_strcasecmp (argv[i], "piv"))
ykapps.piv = 0x80;
else if (!ascii_strcasecmp (argv[i], "oath")
|| !ascii_strcasecmp (argv[i], "oauth"))
ykapps.oath = 0x80;
else if (!ascii_strcasecmp (argv[i], "fido2"))
ykapps.fido2 = 0x80;
else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*"))
{
ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath
= ykapps.fido2 = 0x80;
}
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
}
/* Select the Yubikey Manager application. */
err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0,
NULL, NULL);
if (err)
goto leave;
/* Send the read config command. */
err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen);
if (err)
goto leave;
if (!configlen || *config > configlen - 1)
{
/* The length byte is shorter than the actual length. */
log_error ("Yubikey returned improper config data\n");
log_printhex (config, configlen, "config:");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (configlen-1 > *config)
{
log_info ("Extra config data ignored\n");
log_printhex (config, configlen, "config:");
}
configlen = *config;
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x01, YKAPP_USB_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x03, YKAPP_USB_ENABLED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0d, YKAPP_NFC_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0e, YKAPP_NFC_ENABLED);
if (err)
goto leave;
switch (cmd)
{
case ykLIST: yk_list (fp, &ykapps); break;
case ykENABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 1); break;
case ykDISABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 0); break;
}
leave:
xfree (config);
return err;
}
diff --git a/tools/gpg-card.c b/tools/gpg-card.c
index 4af6d01ff..626579882 100644
--- a/tools/gpg-card.c
+++ b/tools/gpg-card.c
@@ -1,3453 +1,3453 @@
/* gpg-card.c - An interactive tool to work with cards.
* Copyright (C) 2019, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#define INCLUDED_BY_MAIN_MODULE 1
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/ttyio.h"
#include "../common/server-help.h"
#include "../common/openpgpdefs.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
/* Constants to identify the commands and options. */
enum opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oDebug = 500,
oGpgProgram,
oGpgsmProgram,
oStatusFD,
oWithColons,
oNoAutostart,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oXauthority,
oLCctype,
oLCmessages,
oNoKeyLookup,
oDummy
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup",
"use --no-key-lookup for \"list\""),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* An object to create lists of labels and keyrefs. */
struct keyinfolabel_s
{
const char *label;
const char *keyref;
};
typedef struct keyinfolabel_s *keyinfolabel_t;
/* Limit of size of data we read from a file for certain commands. */
#define MAX_GET_DATA_FROM_FILE 16384
/* Constants for OpenPGP cards. */
#define OPENPGP_USER_PIN_DEFAULT "123456"
#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
#define OPENPGP_KDF_DATA_LENGTH_MIN 90
#define OPENPGP_KDF_DATA_LENGTH_MAX 110
/* Local prototypes. */
static void show_keysize_warning (void);
static gpg_error_t dispatch_command (card_info_t info, const char *command);
static void interactive_loop (void);
#ifdef HAVE_LIBREADLINE
static char **command_completion (const char *text, int start, int end);
#endif /*HAVE_LIBREADLINE*/
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "gpg-card"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-card"
" [options] [{[--] command [args]}] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-card"
" [options] [command [args] {-- command [args]}]\n\n"
"Tool to manage cards and tokens. With a command an interactive\n"
"mode is used. Use command \"help\" to list all commands.");
break;
default: p = NULL; break;
}
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Command line parsing. */
static void
parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
{
while (optfile_parse (NULL, NULL, NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break;
case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break;
case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
case oStatusFD:
gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons: opt.with_colons = 1; break;
case oNoAutostart: opt.autostart = 0; break;
case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
case oXauthority: set_opt_session_env ("XAUTHORITY",
pargs->r.ret_str); break;
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case oNoKeyLookup: opt.no_key_lookup = 1; break;
default: pargs->err = 2; break;
}
}
}
/* gpg-card main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
char **command_list = NULL;
int cmdidx;
char *command;
gnupg_reopen_std ("gpg-card");
set_strusage (my_strusage);
gnupg_rl_initialize ();
log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup default options. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
if (log_get_errorcount (0))
exit (2);
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (!opt.gpgsm_program)
opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM);
/* Now build the list of commands. We guess the size of the array
* by assuming each item is a complete command. Obviously this will
* be rarely the case, but it is less code to allocate a possible
* too large array. */
command_list = xcalloc (argc+1, sizeof *command_list);
cmdidx = 0;
command = NULL;
while (argc)
{
for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
{
if (!command)
command = xstrdup (*argv);
else
{
char *tmp = xstrconcat (command, " ", *argv, NULL);
xfree (command);
command = tmp;
}
}
if (argc)
{ /* Skip the double dash. */
argc--;
argv++;
}
if (command)
{
command_list[cmdidx++] = command;
command = NULL;
}
}
opt.interactive = !cmdidx;
if (opt.interactive)
{
interactive_loop ();
err = 0;
}
else
{
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
err = 0;
for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
{
err = dispatch_command (info, command);
if (err)
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0; /* This was a "quit". */
else if (command && !opt.quiet)
log_info ("stopped at command '%s'\n", command);
}
flush_keyblock_cache ();
if (command_list)
{
for (cmdidx=0; command_list[cmdidx]; cmdidx++)
xfree (command_list[cmdidx]);
xfree (command_list);
}
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
gnupg_status_printf (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
* On error return an error code and stores NULL at R_BUFFER; on
* success returns 0 and stores the number of bytes read at R_BUFLEN
* and the address of a newly allocated buffer at R_BUFFER. A
* complementary nul byte is always appended to the data but not
* counted; this allows to pass NULL for R-BUFFER and consider the
* returned data as a string. */
static gpg_error_t
get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
es_fclose (fp);
return err;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp);
es_fclose (fp);
if (n < 0)
{
err = gpg_error_from_syserror ();
tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
xfree (data);
return err;
}
data[n] = 0;
*r_buffer = data;
if (r_buflen)
*r_buflen = n;
return 0;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
* success. */
static gpg_error_t
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
gpg_error_t err;
estream_t fp;
fp = es_fopen (fname, "wb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
es_fclose (fp);
return err;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
return 0;
}
/* Return a malloced string with the number opf the menu PROMPT.
* Control-D is mapped to "Q". */
static char *
get_selection (const char *prompt)
{
char *answer;
tty_printf ("\n");
tty_printf ("%s", prompt);
tty_printf ("\n");
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
strcpy (answer, "q");
return answer;
}
/* Simply prints TEXT to the output. Returns 0 as a convenience.
- * This is a separate fucntion so that it can be extended to run
+ * This is a separate function so that it can be extended to run
* less(1) or so. The extra arguments are int values terminated by a
* 0 to indicate card application types supported with this command.
- * If none are given (just teh final 0), this is a general
+ * If none are given (just the final 0), this is a general
* command. */
static gpg_error_t
print_help (const char *text, ...)
{
estream_t fp;
va_list arg_ptr;
int value;
int any = 0;
fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "%s\n", text);
va_start (arg_ptr, text);
while ((value = va_arg (arg_ptr, int)))
{
if (!any)
tty_fprintf (fp, "[Supported by: ");
tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
any = 1;
}
if (any)
tty_fprintf (fp, "]\n");
va_end (arg_ptr);
return 0;
}
/* Return the OpenPGP card manufacturer name. */
static const char *
get_manufacturer (unsigned int no)
{
/* Note: Make sure that there is no colon or linefeed in the string. */
switch (no)
{
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x0009: return "Fidesmo";
case 0x000A: return "Dangerous Things";
case 0x000B: return "Feitian Technologies";
case 0x002A: return "Magrathea";
case 0x0042: return "GnuPG e.V.";
case 0x1337: return "Warsaw Hackerspace";
case 0x2342: return "warpzone"; /* hackerspace Muenster. */
case 0x4354: return "Confidential Technologies"; /* cotech.de */
case 0x5443: return "TIF-IT e.V.";
case 0x63AF: return "Trustica";
case 0xBD0E: return "Paranoidlabs";
case 0xF517: return "FSIJ";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
* 0xFF00 to 0xFFFE are assigned for use with randomly created
* serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
}
}
/* Print an (OpenPGP) fingerprint. */
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
for (i=0; i < fprlen ; i++, fpr++)
tty_fprintf (fp, "%02X", *fpr);
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
/* Print the keygrip GRP. */
static void
print_keygrip (estream_t fp, const unsigned char *grp)
{
int i;
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
tty_fprintf (fp, "\n");
}
/* Print a string but avoid printing control characters. */
static void
print_string (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
/* Print an ISO formatted name or "[not set]". */
static void
print_isoname (estream_t fp, const char *name)
{
if (name && *name)
{
char *p, *given, *buf;
buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (*buf)
tty_fprintf (fp, " ");
}
if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
tty_fprintf (fp, _("[not set]"));
}
tty_fprintf (fp, "\n");
}
/* Return true if the buffer MEM of length memlen consists only of zeroes. */
static int
mem_is_zero (const char *mem, unsigned int memlen)
{
int i;
for (i=0; i < memlen && !mem[i]; i++)
;
return (i == memlen);
}
/* Helper to list a single keyref. LABEL_KEYREF is a fallback key
* reference if no info is available; it may be NULL. */
static void
list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo,
const char *label_keyref, estream_t fp, int no_key_lookup)
{
gpg_error_t err;
keyblock_t keyblock = NULL;
keyblock_t kb;
pubkey_t pubkey;
userid_t uid;
key_info_t ki;
const char *s;
gcry_sexp_t s_pkey;
int any;
if (firstkinfo && kinfo)
{
tty_fprintf (fp, " ");
if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
{
tty_fprintf (fp, "[none]\n");
tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref);
tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo);
goto leave;
}
print_keygrip (fp, kinfo->grip);
tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
if (kinfo->usage)
{
any = 0;
tty_fprintf (fp, " (");
if ((kinfo->usage & GCRY_PK_USAGE_SIGN))
{ tty_fprintf (fp, "sign"); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_CERT))
{ tty_fprintf (fp, "%scert", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_AUTH))
{ tty_fprintf (fp, "%sauth", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_ENCR))
{ tty_fprintf (fp, "%sencr", any?",":""); any=1; }
tty_fprintf (fp, ")");
}
tty_fprintf (fp, "\n");
if (!scd_readkey (kinfo->keyref, &s_pkey))
{
char *tmp = pubkey_algo_string (s_pkey, NULL);
tty_fprintf (fp, " algorithm ..: %s\n", tmp);
xfree (tmp);
gcry_sexp_release (s_pkey);
s_pkey = NULL;
}
else
{
tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo);
}
if (kinfo->fprlen && kinfo->created)
{
tty_fprintf (fp, " stored fpr .: ");
print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen);
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (kinfo->created));
}
if (no_key_lookup)
err = 0;
else
err = get_matching_keys (kinfo->grip,
(GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
&keyblock);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err));
goto leave;
}
for (kb = keyblock; kb; kb = kb->next)
{
tty_fprintf (fp, " used for ...: %s\n",
kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
pubkey = kb->keys;
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
{
/* If this is not the primary key print the primary
* key's fingerprint or a reference to it. */
tty_fprintf (fp, " main key .: ");
for (ki=firstkinfo; ki; ki = ki->next)
if (pubkey->grip_valid
&& !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (ki)
{
/* Fixme: Replace mapping by a table lookup. */
if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
s = "this";
else if (!strcmp (ki->keyref, "OPENPGP.1"))
s = "Signature key";
else if (!strcmp (ki->keyref, "OPENPGP.2"))
s = "Encryption key";
else if (!strcmp (ki->keyref, "OPENPGP.3"))
s = "Authentication key";
else
s = NULL;
if (s)
tty_fprintf (fp, "<%s>\n", s);
else
tty_fprintf (fp, "<Key %s>\n", ki->keyref);
}
else /* Print the primary key as fallback. */
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
/* Find the primary or subkey of that key. */
for (; pubkey; pubkey = pubkey->next)
if (pubkey->grip_valid
&& !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (pubkey)
{
tty_fprintf (fp, " fpr ......: ");
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
tty_fprintf (fp, " created ..: %s\n",
isotimestamp (pubkey->created));
}
}
for (uid = kb->uids; uid; uid = uid->next)
{
print_string (fp, " user id ..: ", uid->value);
}
}
}
else
{
tty_fprintf (fp, " [none]\n");
if (label_keyref)
tty_fprintf (fp, " keyref .....: %s\n", label_keyref);
if (kinfo)
tty_fprintf (fp, " algorithm ..: %s\n", kinfo->keyalgo);
}
leave:
release_keyblock (keyblock);
}
/* List all keyinfo in INFO using the list of LABELS. */
static void
list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp,
int no_key_lookup)
{
key_info_t kinfo;
int idx, i;
/* Print the keyinfo. We first print those we known and then all
* remaining item. */
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
kinfo->xflag = 0;
if (labels)
{
for (idx=0; labels[idx].label; idx++)
{
tty_fprintf (fp, "%s", labels[idx].label);
kinfo = find_kinfo (info, labels[idx].keyref);
list_one_kinfo (info->kinfo, kinfo, labels[idx].keyref,
fp, no_key_lookup);
if (kinfo)
kinfo->xflag = 1;
}
}
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
{
if (kinfo->xflag)
continue;
tty_fprintf (fp, "Key %s ", kinfo->keyref);
for (i=5+strlen (kinfo->keyref); i < 18; i++)
tty_fprintf (fp, ".");
tty_fprintf (fp, ":");
list_one_kinfo (info->kinfo, kinfo, NULL, fp, no_key_lookup);
}
}
/* List OpenPGP card specific data. */
static void
list_openpgp (card_info_t info, estream_t fp, int no_key_lookup)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "Signature key ....:", "OPENPGP.1" },
{ "Encryption key....:", "OPENPGP.2" },
{ "Authentication key:", "OPENPGP.3" },
{ NULL, NULL }
};
if (!info->serialno
|| strncmp (info->serialno, "D27600012401", 12)
|| strlen (info->serialno) != 32 )
{
tty_fprintf (fp, "invalid OpenPGP card\n");
return;
}
tty_fprintf (fp, "Manufacturer .....: %s\n",
get_manufacturer (xtoi_2(info->serialno+16)*256
+ xtoi_2 (info->serialno+18)));
tty_fprintf (fp, "Name of cardholder: ");
print_isoname (fp, info->disp_name);
print_string (fp, "Language prefs ...: ", info->disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info->disp_sex == 1? _("Mr."):
info->disp_sex == 2? _("Ms.") : "");
print_string (fp, "URL of public key : ", info->pubkey_url);
print_string (fp, "Login data .......: ", info->login_data);
if (info->private_do[0])
print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
if (info->private_do[1])
print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
if (info->private_do[2])
print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
if (info->private_do[3])
print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
if (info->cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
}
if (info->cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
}
if (info->cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info->chv1_cached? _("not forced"): _("forced"));
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]);
tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
if (info->extcap.kdf)
{
tty_fprintf (fp, "KDF setting ......: %s\n",
info->kdf_do_enabled ? "on" : "off");
}
if (info->extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off",
info->uif[2] ? "on" : "off");
}
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup);
}
/* List PIV card specific data. */
static void
list_piv (card_info_t info, estream_t fp, int no_key_lookup)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "PIV authentication:", "PIV.9A" },
{ "Card authenticat. :", "PIV.9E" },
{ "Digital signature :", "PIV.9C" },
{ "Key management ...:", "PIV.9D" },
{ NULL, NULL }
};
const char *s;
int i;
if (info->chvusage[0] || info->chvusage[1])
{
tty_fprintf (fp, "PIN usage policy .:");
if ((info->chvusage[0] & 0x40))
tty_fprintf (fp, " app-pin");
if ((info->chvusage[0] & 0x20))
tty_fprintf (fp, " global-pin");
if ((info->chvusage[0] & 0x10))
tty_fprintf (fp, " occ");
if ((info->chvusage[0] & 0x08))
tty_fprintf (fp, " vci");
if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04))
tty_fprintf (fp, " pairing");
if (info->chvusage[1] == 0x10)
tty_fprintf (fp, " primary:card");
else if (info->chvusage[1] == 0x20)
tty_fprintf (fp, " primary:global");
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "PIN retry counter :");
for (i=0; i < DIM (info->chvinfo); i++)
{
if (info->chvinfo[i] > 0)
tty_fprintf (fp, " %d", info->chvinfo[i]);
else
{
switch (info->chvinfo[i])
{
case -1: s = "[error]"; break;
case -2: s = "-"; break; /* No such PIN or info not available. */
case -3: s = "[blocked]"; break;
case -5: s = "[verified]"; break;
default: s = "[?]"; break;
}
tty_fprintf (fp, " %s", s);
}
}
tty_fprintf (fp, "\n");
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup);
}
static void
print_a_version (estream_t fp, const char *prefix, unsigned int value)
{
unsigned int a, b, c, d;
a = ((value >> 24) & 0xff);
b = ((value >> 16) & 0xff);
c = ((value >> 8) & 0xff);
d = ((value ) & 0xff);
if (a)
tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
else if (b)
tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
else
tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
}
/* Print all available information about the current card. With
* NO_KEY_LOOKUP the sometimes expensive listing of all matching
* OpenPGP and X.509 keys is not done */
static void
list_card (card_info_t info, int no_key_lookup)
{
estream_t fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "Reader ...........: %s\n",
info->reader? info->reader : "[none]");
if (info->cardtype)
tty_fprintf (fp, "Card type ........: %s\n", info->cardtype);
if (info->cardversion)
print_a_version (fp, "Card firmware ....:", info->cardversion);
tty_fprintf (fp, "Serial number ....: %s\n",
info->serialno? info->serialno : "[none]");
tty_fprintf (fp, "Application type .: %s%s%s%s\n",
app_type_string (info->apptype),
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr
? info->apptypestr:"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":"");
if (info->appversion)
print_a_version (fp, "Version ..........:", info->appversion);
if (info->serialno && info->dispserialno
&& strcmp (info->serialno, info->dispserialno))
tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno);
switch (info->apptype)
{
case APP_TYPE_OPENPGP: list_openpgp (info, fp, no_key_lookup); break;
case APP_TYPE_PIV: list_piv (info, fp, no_key_lookup); break;
default: break;
}
}
/* The LIST command. This also updates INFO if needed. */
static gpg_error_t
cmd_list (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_cards, opt_apps, opt_no_key_lookup;
strlist_t cards = NULL;
strlist_t sl;
estream_t fp = opt.interactive? NULL : es_stdout;
int cardno = -1;
char *appstr = NULL;
int count;
int need_learn = 0;
int star;
size_t snlen;
const char *s;
if (!info)
return print_help
("LIST [--cards] [--apps] [--no-key-lookup] [N] [APP]\n\n"
"Show the content of the current card.\n"
"With N given select and list the n-th card;\n"
"with APP also given select that application.\n"
"To select an APP on the current card use '-' for N.\n"
" --cards lists available cards\n"
" --apps lists additional card applications\n"
" --no-key-lookup does not list matching OpenPGP or X.509 keys\n"
, 0);
opt_cards = has_leading_option (argstr, "--cards");
opt_apps = has_leading_option (argstr, "--apps");
opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup");
argstr = skip_options (argstr);
if (opt.no_key_lookup)
opt_no_key_lookup = 1;
if (digitp (argstr) || (*argstr == '-' && spacep (argstr+1)))
{
if (*argstr == '-' && (argstr[1] || spacep (argstr+1)))
argstr++; /* Keep current card. */
else
{
cardno = atoi (argstr);
while (digitp (argstr))
argstr++;
}
while (spacep (argstr))
argstr++;
if (*argstr)
{
appstr = argstr;
while (*argstr && !spacep (argstr))
argstr++;
while (spacep (argstr))
argstr++;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
}
}
else if (*argstr)
{
/* First argument needs to be a digit. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!info->serialno)
{
/* This is probably the first call. We need to send a SERIALNO
* command to scd so that our session knows all cards. */
err = scd_serialno (NULL, NULL);
if (err)
goto leave;
need_learn = 1;
}
if (opt_cards || opt_apps)
{
/* Note that with option --apps CARDS is here the list of all
* apps. Format is "SERIALNO APPNAME {APPNAME}". We print the
* card number in the first column. */
if (opt_apps)
err = scd_applist (&cards, opt_cards);
else
err = scd_cardlist (&cards);
if (err)
goto leave;
for (count = 0, sl = cards; sl; sl = sl->next, count++)
{
if (info && info->serialno)
{
s = strchr (sl->d, ' ');
if (s)
snlen = s - sl->d;
else
snlen = strlen (sl->d);
star = (strlen (info->serialno) == snlen
&& !memcmp (info->serialno, sl->d, snlen));
}
else
star = 0;
tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d);
}
}
else
{
if (cardno != -1)
{
/* Switch to the requested card. */
err = scd_cardlist (&cards);
if (err)
goto leave;
for (count = 0, sl = cards; sl; sl = sl->next, count++)
if (count == cardno)
break;
if (!sl)
{
err = gpg_error (GPG_ERR_INV_INDEX);
goto leave;
}
err = scd_switchcard (sl->d);
need_learn = 1;
}
if (appstr && *appstr)
{
/* Switch to the requested app. */
err = scd_switchapp (appstr);
if (err)
goto leave;
need_learn = 1;
}
if (need_learn)
err = scd_learn (info);
else
err = 0;
if (!err)
list_card (info, opt_no_key_lookup);
}
leave:
free_strlist (cards);
return err;
}
/* The VERIFY command. */
static gpg_error_t
cmd_verify (card_info_t info, char *argstr)
{
gpg_error_t err;
const char *pinref;
if (!info)
return print_help ("verify [chvid]", 0);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = info->serialno;
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
return gpg_error (GPG_ERR_MISSING_VALUE);
err = scd_checkpin (pinref);
if (err)
log_error ("verify failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
return err;
}
static gpg_error_t
cmd_authenticate (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_setkey;
int opt_raw;
char *string = NULL;
char *key = NULL;
size_t keylen;
if (!info)
return print_help
("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n"
- "Perform a mutual autentication either by reading the key\n"
+ "Perform a mutual authentication either by reading the key\n"
"from FILE or by taking it from the command line. Without\n"
"the option --raw the key is expected to be hex encoded.\n"
"To install a new administration key --setkey is used; this\n"
"requires a prior authentication with the old key.",
APP_TYPE_PIV, 0);
if (info->apptype != APP_TYPE_PIV)
{
log_info ("Note: This is a PIV only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
opt_setkey = has_leading_option (argstr, "--setkey");
opt_raw = has_leading_option (argstr, "--raw");
argstr = skip_options (argstr);
if (*argstr == '<') /* Read key from a file. */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &string, NULL);
if (err)
goto leave;
}
if (opt_raw)
{
key = string? string : xstrdup (argstr);
string = NULL;
keylen = strlen (key);
}
else
{
key = hex_to_buffer (string? string: argstr, &keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen);
leave:
if (key)
{
wipememory (key, keylen);
xfree (key);
}
xfree (string);
return err;
}
/* Helper for cmd_name to qyery a part of name. */
static char *
ask_one_name (const char *prompt)
{
char *name;
int i;
for (;;)
{
name = tty_get (prompt);
trim_spaces (name);
tty_kill_prompt ();
if (!*name || *name == CONTROL_D)
{
if (*name == CONTROL_D)
tty_fprintf (NULL, "\n");
xfree (name);
return NULL;
}
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
* to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
/* The NAME command. */
static gpg_error_t
cmd_name (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *surname, *givenname;
char *isoname, *p;
if (!info)
return print_help
("name [--clear]\n\n"
"Set the name field of an OpenPGP card. With --clear the stored\n"
"name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
again:
if (!strcmp (argstr, "--clear"))
isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
surname = ask_one_name (_("Cardholder's surname: "));
givenname = ask_one_name (_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return gpg_error (GPG_ERR_CANCELED);
}
isoname = xstrconcat (surname, "<<", givenname, NULL);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
log_info (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
goto again;
}
}
err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
xfree (isoname);
return err;
}
static gpg_error_t
cmd_url (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *url;
if (!info)
return print_help
("URL [--clear]\n\n"
"Set the URL data object. That data object can be used by\n"
"the FETCH command to retrieve the full public key. The\n"
"option --clear deletes the content of that data object.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!strcmp (argstr, "--clear"))
url = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
url = tty_get (_("URL to retrieve public key: "));
trim_spaces (url);
tty_kill_prompt ();
if (!*url || *url == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr ("PUBKEY-URL", url, strlen (url));
leave:
xfree (url);
return err;
}
/* Fetch the key from the URL given on the card or try to get it from
* the default keyserver. */
static gpg_error_t
cmd_fetch (card_info_t info)
{
gpg_error_t err;
key_info_t kinfo;
if (!info)
return print_help
("FETCH\n\n"
"Retrieve a key using the URL data object or if that is missing\n"
"using the fingerprint.", APP_TYPE_OPENPGP, 0);
if (info->pubkey_url && *info->pubkey_url)
{
/* strlist_t sl = NULL; */
/* add_to_strlist (&sl, info.pubkey_url); */
/* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
/* free_strlist (sl); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen)
{
/* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
/* opt.keyserver, 0); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else
err = gpg_error (GPG_ERR_NO_DATA);
return err;
}
static gpg_error_t
cmd_login (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data;
size_t datalen;
if (!info)
return print_help
("LOGIN [--clear] [< FILE]\n\n"
"Set the login data object. If FILE is given the data is\n"
"is read from that file. This allows for binary data.\n"
"The option --clear deletes the login data.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
{
data = xstrdup (" "); /* kludge. */
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else
{
data = tty_get (_("Login data (account name): "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
datalen = strlen (data);
}
err = scd_setattr ("LOGIN-DATA", data, datalen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_lang (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data, *p;
if (!info)
return print_help
("LANG [--clear]\n\n"
"Change the language info for the card. This info can be used\n"
"by applications for a personalized greeting. Up to 4 two-digit\n"
"language identifiers can be entered as a preference. The option\n"
"--clear removes all identifiers. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
data = xstrdup (" "); /* Note that we need two spaces here. */
else
{
again:
data = tty_get (_("Language preferences: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (strlen (data) > 8 || (strlen (data) & 1))
{
log_info (_("Error: invalid length of preference string.\n"));
xfree (data);
goto again;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
log_info (_("Error: invalid characters in preference string.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-LANG", data, strlen (data));
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_salut (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *str;
if (!info)
return print_help
("SALUT [--clear]\n\n"
"Change the salutation info for the card. This info can be used\n"
"by applications for a personalized greeting. The option --clear\n"
"removes this data object. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
again:
if (!strcmp (argstr, "--clear"))
str = "9";
else
{
data = tty_get (_("Salutation (M = Mr., F = Ms., or space): "));
trim_spaces (data);
tty_kill_prompt ();
if (*data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-SEX", str, 1);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_cafpr (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *s;
int i, c;
unsigned char fpr[32];
int fprlen;
int fprno;
int opt_clear = 0;
if (!info)
return print_help
("CAFPR [--clear] N\n\n"
"Change the CA fingerprint number N. N must be in the\n"
"range 1 to 3. The option --clear clears the specified\n"
"CA fingerprint N or all of them if N is 0 or not given.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
fprno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
fprno = 0;
if (opt_clear && !fprno)
; /* Okay: clear all fprs. */
else if (fprno < 1 || fprno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
again:
if (opt_clear)
{
memset (fpr, 0, 20);
fprlen = 20;
}
else
{
xfree (data);
data = tty_get (_("CA fingerprint: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
for (i=0, s=data; i < sizeof fpr && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
if ((fprlen != 20 && fprlen != 32) || *s)
{
log_error (_("Error: invalid formatted fingerprint.\n"));
goto again;
}
}
if (!fprno)
{
log_assert (opt_clear);
err = scd_setattr ("CA-FPR-1", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-2", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-3", fpr, fprlen);
}
else
err = scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, fprlen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_privatedo (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *do_name = NULL;
char *data = NULL;
size_t datalen;
int do_no;
if (!info)
return print_help
("PRIVATEDO [--clear] N [< FILE]\n\n"
"Change the private data object N. N must be in the\n"
"range 1 to 4. If FILE is given the data is is read\n"
"from that file. The option --clear clears the data.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
do_no = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
do_no = 0;
if (do_no < 1 || do_no > 4)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_name = xasprintf ("PRIVATE-DO-%d", do_no);
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else if (*argstr)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
else
{
data = tty_get (_("Private DO data: "));
trim_spaces (data);
tty_kill_prompt ();
datalen = strlen (data);
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr (do_name, data, datalen);
leave:
xfree (do_name);
xfree (data);
return err;
}
static gpg_error_t
cmd_writecert (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *certref_buffer = NULL;
char *certref;
char *data = NULL;
size_t datalen;
if (!info)
return print_help
("WRITECERT [--clear] CERTREF < FILE\n\n"
"Write a certificate for key 3. Unless --clear is given\n"
"the file argument is mandatory. The option --clear removes\n"
"the certificate from the card.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
goto leave;
}
certref = certref_buffer = xstrdup ("OPENPGP.3");
}
else /* Upcase the certref; prepend cardtype if needed. */
{
if (!strchr (certref, '.'))
certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
certref, NULL);
else
certref_buffer = xstrdup (certref);
ascii_strupr (certref_buffer);
certref = certref_buffer;
}
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----")
&& ascii_memistr (data, datalen, "-----END CERTIFICATE-----")
&& !memchr (data, 0, datalen) && !memchr (data, 1, datalen))
{
struct b64state b64;
err = b64dec_start (&b64, "");
if (!err)
err = b64dec_proc (&b64, data, datalen, &datalen);
if (!err)
err = b64dec_finish (&b64);
if (err)
goto leave;
}
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writecert (certref, data, datalen);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_readcert (card_info_t info, char *argstr)
{
gpg_error_t err;
char *certref_buffer = NULL;
char *certref;
void *data = NULL;
size_t datalen;
const char *fname;
if (!info)
return print_help
("READCERT CERTREF > FILE\n\n"
"Read the certificate for key CERTREF and store it in FILE.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
goto leave;
}
certref = certref_buffer = xstrdup ("OPENPGP.3");
}
if (*argstr == '>') /* Write it to a file */
{
for (argstr++; spacep (argstr); argstr++)
;
fname = argstr;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_readcert (certref, &data, &datalen);
if (err)
goto leave;
err = put_data_to_file (fname, data, datalen);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_writekey (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_force;
char *argv[2];
int argc;
char *keyref_buffer = NULL;
char *keyref;
char *keygrip;
if (!info)
return print_help
("WRITEKEY [--force] KEYREF KEYGRIP\n\n"
"Write a private key object identified by KEYGRIP to slot KEYREF.\n"
"Use --force to overwrite an existing key.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_force = has_leading_option (argstr, "--force");
argstr = skip_options (argstr);
argc = split_fields (argstr, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Upcase the keyref; prepend cardtype if needed. */
keyref = argv[0];
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
/* Get the keygrip. */
keygrip = argv[1];
if (strlen (keygrip) != 40
&& !(keygrip[0] == '&' && strlen (keygrip+1) == 40))
{
log_error (_("Not a valid keygrip (expecting 40 hex digits)\n"));
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writekey (keyref, opt_force, keygrip);
leave:
xfree (keyref_buffer);
return err;
}
static gpg_error_t
cmd_forcesig (card_info_t info)
{
gpg_error_t err;
int newstate;
if (!info)
return print_help
("FORCESIG\n\n"
"Toggle the forcesig flag of an OpenPGP card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
newstate = !info->chv1_cached;
err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (err)
goto leave;
/* Read it back to be sure we have the right toggle state the next
* time. */
err = scd_getattr ("CHV-STATUS", info);
leave:
return err;
}
-/* Helper for cmd_generate_openpgp. Nore that either 0 or 1 is stored at
+/* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at
* FORCED_CHV1. */
static gpg_error_t
check_pin_for_key_operation (card_info_t info, int *forced_chv1)
{
gpg_error_t err = 0;
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
* don't get bothered with PIN queries for each self-signature. */
err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (err)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = -1; /* Not changed. */
goto leave;
}
}
/* Check the PIN now, so that we won't get asked later for each
* binding signature. */
err = scd_checkpin (info->serialno);
if (err)
log_error ("error checking the PIN: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Helper for cmd_generate_openpgp. */
static void
restore_forced_chv1 (int *forced_chv1)
{
gpg_error_t err;
/* Note the possible values stored at FORCED_CHV1:
* 0 - forcesig was not enabled.
* 1 - forcesig was enabled - enable it again.
* -1 - We have not changed anything. */
if (*forced_chv1 == 1)
{ /* Switch back to forced state. */
err = scd_setattr ("CHV-STATUS-1", "", 1);
if (err)
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = 0;
}
}
/* Ask whether existing keys shall be overwritten. With NULL used for
* KINFO it will ask for all keys, other wise for the given key. */
static gpg_error_t
ask_replace_keys (key_info_t kinfo)
{
gpg_error_t err;
char *answer;
tty_printf ("\n");
if (kinfo)
log_info (_("Note: key %s is already stored on the card!\n"),
kinfo->keyref);
else
log_info (_("Note: Keys are already stored on the card!\n"));
tty_printf ("\n");
if (kinfo)
answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref);
else
answer = tty_get (_("Replace existing keys? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
err = gpg_error (GPG_ERR_CANCELED);
else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
err = gpg_error (GPG_ERR_CANCELED);
else
err = 0;
xfree (answer);
return err;
}
/* Implementation of cmd_generate for OpenPGP cards to generate all
* standard keys at once. */
static gpg_error_t
generate_all_openpgp_card_keys (card_info_t info, char **algos)
{
gpg_error_t err;
int forced_chv1 = -1;
int want_backup;
char *answer = NULL;
key_info_t kinfo1, kinfo2, kinfo3;
if (info->extcap.ki)
{
xfree (answer);
answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else
want_backup = 0;
kinfo1 = find_kinfo (info, "OPENPGP.1");
kinfo2 = find_kinfo (info, "OPENPGP.2");
kinfo3 = find_kinfo (info, "OPENPGP.3");
if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen))
|| (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen))
|| (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen))
)
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
}
/* If no displayed name has been set, we assume that this is a fresh
* card and print a hint about the default PINs. */
if (!info->disp_name || !*info->disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
tty_printf ("\n");
}
err = check_pin_for_key_operation (info, &forced_chv1);
if (err)
goto leave;
(void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */
/* FIXME: We need to divert to a function which spawns gpg which
* will then create the key. This also requires new features in
* gpg. We might also first create the keys on the card and then
* tell gpg to use them to create the OpenPGP keyblock. */
/* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
(void)want_backup;
err = scd_genkey ("OPENPGP.1", 1, NULL, NULL);
leave:
restore_forced_chv1 (&forced_chv1);
xfree (answer);
return err;
}
/* Create a single key. This is a helper for cmd_generate. */
static gpg_error_t
generate_key (card_info_t info, const char *keyref, int force,
const char *algo)
{
gpg_error_t err;
key_info_t kinfo;
if (info->apptype == APP_TYPE_OPENPGP)
{
kinfo = find_kinfo (info, keyref);
if (!kinfo)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!force
&& kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen))
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
force = 1;
}
}
err = scd_genkey (keyref, force, algo, NULL);
leave:
return err;
}
static gpg_error_t
cmd_generate (card_info_t info, char *argstr)
{
static char * const valid_algos[] =
{ "rsa2048", "rsa3072", "rsa4096", "",
"nistp256", "nistp384", "nistp521", "",
"brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "",
"ed25519", "cv25519",
NULL
};
gpg_error_t err;
int opt_force;
char *p;
char **opt_algo = NULL; /* Malloced. */
char *keyref_buffer = NULL; /* Malloced. */
char *keyref; /* Points into argstr or keyref_buffer. */
int i, j;
if (!info)
return print_help
("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n"
"Create a new key on a card.\n"
"Use --force to overwrite an existing key.\n"
"Use \"help\" for ALGO to get a list of known algorithms.\n"
"For OpenPGP cards several algos may be given.\n"
"Note that the OpenPGP key generation is done interactively\n"
"unless a single ALGO or KEYREF are given.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
opt_force = has_leading_option (argstr, "--force");
err = get_option_value (argstr, "--algo", &p);
if (err)
goto leave;
if (p)
{
opt_algo = strtokenize (p, "+");
if (!opt_algo)
{
err = gpg_error_from_syserror ();
xfree (p);
goto leave;
}
xfree (p);
}
argstr = skip_options (argstr);
keyref = argstr;
if ((argstr = strchr (keyref, ' ')))
{
*argstr++ = 0;
trim_spaces (keyref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = keyref + strlen (keyref);
if (!*keyref)
keyref = NULL;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_algo)
{
/* opt_algo is an array of algos. */
for (i=0; opt_algo[i]; i++)
{
for (j=0; valid_algos[j]; j++)
if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i]))
break;
if (!valid_algos[j])
{
int lf = 1;
if (!ascii_strcasecmp (opt_algo[i], "help"))
log_info ("Known algorithms:\n");
else
{
log_info ("Invalid algorithm '%s' given. Use one of:\n",
opt_algo[i]);
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
for (i=0; valid_algos[i]; i++)
{
if (!*valid_algos[i])
lf = 1;
else if (lf)
{
lf = 0;
log_info (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
else
log_printf (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
show_keysize_warning ();
goto leave;
}
}
}
/* Upcase the keyref; if it misses the cardtype, prepend it. */
if (keyref)
{
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
}
/* Special checks. */
if ((info->cardtype && !strcmp (info->cardtype, "yubikey"))
&& info->cardversion >= 0x040200 && info->cardversion < 0x040305)
{
log_error ("On-chip key generation on this YubiKey has been blocked.\n");
log_info ("Please see <https://yubi.co/ysa201701> for details\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Divert to dedicated functions. */
if (info->apptype == APP_TYPE_OPENPGP
&& !keyref
&& (!opt_algo || (opt_algo[0] && opt_algo[1])))
{
/* With no algo requested or more than one algo requested and no
* keyref given we create all keys. */
if (opt_force || keyref)
log_info ("Note: OpenPGP key generation is interactive.\n");
err = generate_all_openpgp_card_keys (info, opt_algo);
}
else if (!keyref)
err = gpg_error (GPG_ERR_INV_ID);
else if (opt_algo && opt_algo[0] && opt_algo[1])
{
log_error ("only one algorithm expected as value for --algo.\n");
err = gpg_error (GPG_ERR_INV_ARG);
}
else
err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL);
leave:
xfree (opt_algo);
xfree (keyref_buffer);
return err;
}
/* Change a PIN. */
static gpg_error_t
cmd_passwd (card_info_t info, char *argstr)
{
gpg_error_t err = 0;
char *answer = NULL;
const char *pinref = NULL;
int reset_mode = 0;
int menu_used = 0;
if (!info)
return print_help
("PASSWD [PINREF]\n\n"
"Change or unblock the PINs. Note that in interactive mode\n"
"and without a PINREF a menu is presented for certain cards;\n"
"in non-interactive and without a PINREF a default value is\n"
"used for these cards.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (*argstr)
pinref = argstr;
else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - unblock and set new a PIN\n"
"3 - change the Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
if (strlen (answer) != 1)
continue;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "OPENPGP.1";
else if (*answer == '2')
{ pinref = "OPENPGP.1"; reset_mode = 1; }
else if (*answer == '3')
pinref = "OPENPGP.3";
else if (*answer == '4')
{ pinref = "OPENPGP.2"; reset_mode = 1; }
}
}
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = "OPENPGP.1";
else if (opt.interactive && info->apptype == APP_TYPE_PIV)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - change the PUK\n"
"3 - change the Global PIN\n"
"Q - quit\n");
if (strlen (answer) != 1)
;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "PIV.80";
else if (*answer == '2')
pinref = "PIV.81";
else if (*answer == '3')
pinref = "PIV.00";
}
}
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = scd_change_pin (pinref, reset_mode);
if (err)
{
if (!opt.interactive && !menu_used && !opt.verbose)
;
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_error ("Error changing the PUK.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_error ("Error unblocking the PIN.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_error ("Error setting the Reset Code.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_error ("Error changing the Admin PIN.\n");
else
log_error ("Error changing the PIN.\n");
}
else
{
if (!opt.interactive && !opt.verbose)
;
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_info ("PUK changed.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_info ("PIN unblocked and new PIN set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_info ("Reset Code set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_info ("Admin PIN changed.\n");
else
log_info ("PIN changed.\n");
}
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_unblock (card_info_t info)
{
gpg_error_t err = 0;
if (!info)
return print_help
("UNBLOCK\n\n"
"Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n"
"cards prior to version 2 can't use this; instead the PASSWD\n"
"command can be used to set a new PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!info->is_v2)
{
log_error (_("This command is only available for version 2 cards\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (!info->chvinfo[1])
{
log_error (_("Reset Code not or not anymore available\n"));
err = gpg_error (GPG_ERR_PIN_BLOCKED);
}
else
{
err = scd_change_pin ("OPENPGP.2", 0);
if (!err)
log_info ("PIN changed.\n");
}
}
else if (info->apptype == APP_TYPE_PIV)
{
/* Unblock the Application PIN. */
err = scd_change_pin ("PIV.80", 1);
if (!err)
log_info ("PIN unblocked and changed.\n");
}
else
{
log_info ("Unblocking not supported for '%s'.\n",
app_type_string (info->apptype));
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return err;
}
/* Note: On successful execution a redisplay should be scheduled. If
* this function fails the card may be in an unknown state. */
static gpg_error_t
cmd_factoryreset (card_info_t info)
{
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int any_apdu = 0;
int is_yubikey = 0;
int i;
if (!info)
return print_help
("FACTORY-RESET\n\n"
"Do a complete reset of some OpenPGP and PIV cards. This\n"
"deletes all data and keys and resets the PINs to their default.\n"
"This is mainly used by developers with scratch cards. Don't\n"
"worry, you need to confirm before the command proceeds.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
/* We support the factory reset for most OpenPGP cards and Yubikeys
* with the PIV application. */
if (info->apptype == APP_TYPE_OPENPGP)
;
else if (info->apptype == APP_TYPE_PIV
&& info->cardtype && !strcmp (info->cardtype, "yubikey"))
is_yubikey = 1;
else
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* For an OpenPGP card the code below basically does the same what
* this gpg-connect-agent script does:
*
* scd reset
* scd serialno undefined
* scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 e6 00 00
* scd apdu 00 44 00 00
* scd reset
* /echo Card has been reset to factory defaults
*
* For a PIV application on a Yubikey it merely issues the Yubikey
* specific resset command.
*/
err = scd_learn (info);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!termstate || is_yubikey)
{
if (!is_yubikey)
{
if (!(info->status_indicator == 3 || info->status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it
* is not possible to select a card application in
* termination state. */
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
}
tty_printf ("\n");
log_info
(_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
xfree (answer);
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
trim_spaces (answer);
if (*answer == CONTROL_D
|| !answer_is_yes_no_default (answer, 0/*(default to no)*/))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
xfree (answer);
answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
tty_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (is_yubikey)
{
/* The PIV application si already selected, we only need to
* send the special reset APDU after having blocked PIN and
* PUK. Note that blocking the PUK is done using the
* unblock PIN command. */
any_apdu = 1;
for (i=0; i < 5; i++)
send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 5; i++)
send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"RESET RETRY COUNTER", 0xffff, NULL, NULL);
err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
if (err)
goto leave;
}
else /* OpenPGP card. */
{
any_apdu = 1;
/* We need to select a card application before we can send APDUs
* to the card without scdaemon doing anything on its own. */
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
NULL, NULL);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
* counter to zero. We can't easily use the card version 2.1
* feature of presenting the admin PIN to allow the terminate
* command because there is no machinery in scdaemon to catch
* the verify command and ask for the PIN when the "APDU"
* command is used.
* Here, the length of dummy wrong PIN is 32-byte, also
* supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
if (err)
goto leave;
}
}
if (!is_yubikey)
{
any_apdu = 1;
/* Send activate datafile command. This is used without
* confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
if (err)
goto leave;
}
/* Finally we reset the card reader once more. */
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
/* Then, connect the card again. */
err = scd_serialno (NULL, NULL);
leave:
if (err && any_apdu && !is_yubikey)
{
log_info ("Due to an error the card might be in an inconsistent state\n"
"You should run the LIST command to check this.\n");
/* FIXME: We need a better solution in the case that the card is
* in a termination state, i.e. the card was removed before the
* activate was sent. The best solution I found with v2.1
* Zeitcontrol card was to kill scdaemon and the issue this
* sequence with gpg-connect-agent:
* scd reset
* scd serialno undefined
* scd apdu 00A4040006D27600012401 (returns error)
* scd apdu 00440000
* Then kill scdaemon again and issue:
* scd reset
* scd serialno openpgp
*/
}
xfree (answer);
return err;
}
/* Generate KDF data. This is a helper for cmd_kdfsetup. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
gpg_error_t err;
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
strlen (OPENPGP_USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
strlen (OPENPGP_ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
static gpg_error_t
cmd_kdfsetup (card_info_t info, char *argstr)
{
gpg_error_t err;
unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
int single = (*argstr != 0);
if (!info)
return print_help
("KDF-SETUP\n\n"
"Prepare the OpenPGP card KDF feature for this card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!info->extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave;
err = scd_setattr ("KDF", kdf_data,
single ? OPENPGP_KDF_DATA_LENGTH_MIN
/* */ : OPENPGP_KDF_DATA_LENGTH_MAX);
if (err)
goto leave;
err = scd_getattr ("KDF", info);
leave:
return err;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
static gpg_error_t
cmd_uif (card_info_t info, char *argstr)
{
gpg_error_t err;
int keyno;
if (!info)
return print_help
("UIF N [on|off|permanent]\n\n"
"Change the User Interaction Flag. N must in the range 1 to 3.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
argstr = skip_options (argstr);
if (digitp (argstr))
{
keyno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
keyno = 0;
if (keyno < 1 || keyno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = GPG_ERR_NOT_IMPLEMENTED;
leave:
return err;
}
static gpg_error_t
cmd_yubikey (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
estream_t fp = opt.interactive? NULL : es_stdout;
char *words[20];
int nwords;
if (!info)
return print_help
("YUBIKEY <cmd> args\n\n"
"Various commands pertaining to Yubikey tokens with <cmd> being:\n"
"\n"
" LIST \n"
"\n"
"List supported and enabled applications.\n"
"\n"
" ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
" DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
"\n"
"Enable or disable the specified or all applications on the\n"
"given interface.",
0);
argstr = skip_options (argstr);
if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
{
log_info ("This command can only be used with Yubikeys.\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
nwords = split_fields (argstr, words, DIM (words));
if (nwords < 1)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Note that we always do a learn to get a chance to the card back
* into a usable state. */
err = yubikey_commands (info, fp, nwords, words);
err2 = scd_learn (info);
if (err2)
log_error ("Error re-reading card: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Data used by the command parser. This needs to be outside of the
* function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
cmdUIF, cmdAUTH, cmdYUBIKEY,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
const char *desc;
} cmds[] = {
{ "quit" , cmdQUIT, N_("quit this menu")},
{ "q" , cmdQUIT, NULL },
{ "help" , cmdHELP, N_("show this help")},
{ "?" , cmdHELP, NULL },
{ "list" , cmdLIST, N_("list all available data")},
{ "l" , cmdLIST, NULL },
{ "name" , cmdNAME, N_("change card holder's name")},
{ "url" , cmdURL, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN, N_("change the login name")},
{ "lang" , cmdLANG, N_("change the language preferences")},
{ "salutation",cmdSALUT, N_("change card holder's salutation")},
{ "salut" , cmdSALUT, NULL },
{ "cafpr" , cmdCAFPR , N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, N_("generate new keys")},
{ "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")},
{ "authenticate",cmdAUTH, N_("authenticate to the card")},
{ "auth" , cmdAUTH, NULL },
{ "reset" , cmdRESET, N_("send a reset to the card daemon")},
{ "factory-reset",cmdFACTRST, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")},
{ "uif", cmdUIF, N_("change the User Interaction Flag")},
{ "privatedo", cmdPRIVATEDO, N_("change a private data object")},
{ "readcert", cmdREADCERT, N_("read a certificate from a data object")},
{ "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
{ NULL, cmdINVCMD, NULL }
};
/* The command line command dispatcher. */
static gpg_error_t
dispatch_command (card_info_t info, const char *orig_command)
{
gpg_error_t err = 0;
enum cmdids cmd; /* The command. */
char *command; /* A malloced copy of ORIG_COMMAND. */
char *argstr; /* The argument as a string. */
int i;
int ignore_error;
if ((ignore_error = *orig_command == '-'))
orig_command++;
command = xstrdup (orig_command);
argstr = NULL;
if ((argstr = strchr (command, ' ')))
{
*argstr++ = 0;
trim_spaces (command);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (command, cmds[i].name ))
break;
cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
/* Make sure we have valid strings for the args. They are allowed
* to be modified and must thus point to a buffer. */
if (!argstr)
argstr = command + strlen (command);
/* For most commands we need to make sure that we have a card. */
if (!info)
; /* Help mode */
else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)
&& !info->initialized)
{
err = scd_learn (info);
if (err)
{
log_error ("Error reading card: %s\n", gpg_strerror (err));
goto leave;
}
}
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Stop processing.", 0);
else
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
dispatch_command (NULL, argstr);
else
{
es_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
es_printf ("Prefix a command with a dash to ignore its error.\n");
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL);
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY: err = cmd_verify (info, argstr); break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdINVCMD:
default:
log_error (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
leave:
/* Return GPG_ERR_EOF only if its origin was "quit". */
es_fflush (es_stdout);
if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
err = gpg_error (GPG_ERR_GENERAL);
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
if (ignore_error)
{
log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
err = 0;
}
else
log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
}
xfree (command);
return err;
}
/* The interactive main loop. */
static void
interactive_loop (void)
{
gpg_error_t err;
char *answer = NULL; /* The input line. */
enum cmdids cmd = cmdNOP; /* The command. */
char *argstr; /* The argument as a string. */
int redisplay = 1; /* Whether to redisplay the main info. */
char *help_arg = NULL; /* Argument of the HELP command. */
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
char *p;
int i;
/* In the interactive mode we do not want to print the program prefix. */
log_set_prefix (NULL, 0);
for (;;)
{
if (help_arg)
{
/* Clear info to indicate helpmode */
info = NULL;
}
else if (!info)
{
/* Get out of help. */
info = &info_buffer;
help_arg = NULL;
redisplay = 0;
}
else if (redisplay)
{
err = cmd_list (info, "");
if (err)
log_error ("Error reading card: %s\n", gpg_strerror (err));
else
{
tty_printf("\n");
redisplay = 0;
}
}
if (!info)
{
/* Copy the pending help arg into our answer. Note that
* help_arg points into answer. */
p = xstrdup (help_arg);
help_arg = NULL;
xfree (answer);
answer = p;
}
else
{
do
{
xfree (answer);
tty_enable_completion (command_completion);
answer = tty_get (_("gpg/card> "));
tty_kill_prompt();
tty_disable_completion ();
trim_spaces(answer);
}
while ( *answer == '#' );
}
argstr = NULL;
if (!*answer)
cmd = cmdLIST; /* We default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((argstr = strchr (answer,' ')))
{
*argstr++ = 0;
trim_spaces (answer);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
}
/* Make sure we have valid strings for the args. They are
* allowed to be modified and must thus point to a buffer. */
if (!argstr)
argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD))
{
/* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */
if (!info)
; /* In help mode. */
else if (redisplay)
{
cmd = cmdLIST;
}
else if (!info->serialno)
{
/* Without a serial number most commands won't work.
* Catch it here. */
tty_printf ("\n");
tty_printf ("Serial number missing\n");
continue;
}
}
err = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Leave this tool.", 0);
else
{
tty_printf ("\n");
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
help_arg = argstr; /* Trigger help for a command. */
else
{
tty_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL);
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY:
err = cmd_verify (info, argstr);
if (!err)
redisplay = 1;
break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST:
err = cmd_factoryreset (info);
if (!err)
redisplay = 1;
break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
if (gpg_err_code (err) == GPG_ERR_CANCELED)
tty_fprintf (NULL, "\n");
else if (err)
{
const char *s = "?";
for (i=0; cmds[i].name; i++ )
if (cmd == cmds[i].id)
{
s = cmds[i].name;
break;
}
log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
}
} /* End of main menu loop. */
leave:
release_card_info (info);
xfree (answer);
}
#ifdef HAVE_LIBREADLINE
/* Helper function for readline's command completion. */
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
* saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen(text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text. */
if (cmds[list_index++].desc && !strncmp (name, text, len))
return strdup(name);
}
return NULL;
}
/* Second helper function for readline's command completion. */
static char **
command_completion (const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
* If not, just do nothing for now. The support for help completion
* needs to be more smarter. */
if (!start)
return rl_completion_matches (text, command_generator);
else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5))
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
index dee5d5d47..43cea1dd1 100644
--- a/tools/gpg-check-pattern.c
+++ b/tools/gpg-check-pattern.c
@@ -1,494 +1,494 @@
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
# include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <regex.h>
#include <ctype.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oVerbose = 'v',
oArmor = 'a',
oPassphrase = 'P',
oProtect = 'p',
oUnprotect = 'u',
oNull = '0',
oNoVerbose = 500,
oCheck,
oHomedir
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, N_("@Options:\n ") },
{ oVerbose, "verbose", 0, "verbose" },
{ oHomedir, "homedir", 2, "@" },
{ oCheck, "check", 0, "run only a syntax check on the patternfile" },
{ oNull, "null", 0, "input is expected to be null delimited" },
ARGPARSE_end ()
};
/* Global options are accessed through the usual OPT structure. */
static struct
{
int verbose;
const char *homedir;
int checkonly;
int null;
} opt;
enum {
PAT_NULL, /* Indicates end of the array. */
PAT_STRING, /* The pattern is a simple string. */
PAT_REGEX /* The pattern is an extended regular expression. */
};
/* An object to decibe an item of our pattern table. */
struct pattern_s
{
int type;
unsigned int lineno; /* Line number of the pattern file. */
union {
struct {
const char *string; /* Pointer to the actual string (nul termnated). */
size_t length; /* The length of this string (strlen). */
} s; /*PAT_STRING*/
struct {
/* We allocate the regex_t because this type is larger than what
we need for PAT_STRING and we expect only a few regex in a
patternfile. It would be a waste of core to have so many
unused stuff in the table. */
regex_t *regex;
} r; /*PAT_REGEX*/
} u;
};
typedef struct pattern_s pattern_t;
/*** Local prototypes ***/
static char *read_file (const char *fname, size_t *r_length);
static pattern_t *parse_pattern_file (char *data, size_t datalen);
static void process (FILE *fp, pattern_t *patarray);
/* Info function for usage(). */
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpg-check-pattern (@GnuPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
break;
case 41:
p = _("Syntax: gpg-check-pattern [options] patternfile\n"
"Check a passphrase given on stdin against the patternfile\n");
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
char *raw_pattern;
size_t raw_pattern_length;
pattern_t *patternarray;
early_system_init ();
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-check-pattern", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* (do not remove the args) */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oCheck: opt.checkonly = 1; break;
case oNull: opt.null = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
exit (2);
if (argc != 1)
usage (1);
/* We read the entire pattern file into our memory and parse it
using a separate function. This allows us to eventual do the
reading while running setuid so that the pattern file can be
hidden from regular users. I am not sure whether this makes
sense, but lets be prepared for it. */
raw_pattern = read_file (*argv, &raw_pattern_length);
if (!raw_pattern)
exit (2);
patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
if (!patternarray)
exit (1);
if (opt.checkonly)
return 0;
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (stdin) , O_BINARY );
#endif
process (stdin, patternarray);
return log_get_errorcount(0)? 1 : 0;
}
/* Read a file FNAME into a buffer and return that malloced buffer.
Caller must free the buffer. On error NULL is returned, on success
the valid length of the buffer is stored at R_LENGTH. The returned
- buffer is guarnteed to be nul terminated. */
+ buffer is guaranteed to be nul terminated. */
static char *
read_file (const char *fname, size_t *r_length)
{
FILE *fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(fp) , O_BINARY );
#endif
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize+1);
else
buf = xrealloc (buf, bufsize+1);
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (fileno(fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
fclose (fp);
xfree (buf);
return NULL;
}
fclose (fp);
}
buf[buflen] = 0;
*r_length = buflen;
return buf;
}
static char *
get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
regerror (errcode, compiled, buffer, length);
return buffer;
}
/* Parse the pattern given in the memory aread DATA/DATALEN and return
a new pattern array. The end of the array is indicated by a NULL
entry. On error an error message is printed and the function
returns NULL. Note that the function modifies DATA and assumes
that data is nul terminated (even if this is one byte past
DATALEN). */
static pattern_t *
parse_pattern_file (char *data, size_t datalen)
{
char *p, *p2;
size_t n;
pattern_t *array;
size_t arraysize, arrayidx;
unsigned int lineno = 0;
/* Estimate the number of entries by counting the non-comment lines. */
arraysize = 0;
p = data;
for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
if (*p != '#')
arraysize++;
arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
array = xcalloc (arraysize, sizeof *array);
arrayidx = 0;
/* Loop over all lines. */
while (datalen && data)
{
lineno++;
p = data;
p2 = data = memchr (p, '\n', datalen);
if (p2)
{
*data++ = 0;
datalen -= data - p;
}
else
p2 = p + datalen;
assert (!*p2);
p2--;
while (isascii (*p) && isspace (*p))
p++;
if (*p == '#')
continue;
while (p2 > p && isascii (*p2) && isspace (*p2))
*p2-- = 0;
if (!*p)
continue;
assert (arrayidx < arraysize);
array[arrayidx].lineno = lineno;
if (*p == '/')
{
int rerr;
p++;
array[arrayidx].type = PAT_REGEX;
if (*p && p[strlen(p)-1] == '/')
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
rerr = regcomp (array[arrayidx].u.r.regex, p,
REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (rerr)
{
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
xfree (rerrbuf);
if (!opt.checkonly)
exit (1);
}
}
else
{
array[arrayidx].type = PAT_STRING;
array[arrayidx].u.s.string = p;
array[arrayidx].u.s.length = strlen (p);
}
arrayidx++;
}
assert (arrayidx < arraysize);
array[arrayidx].type = PAT_NULL;
return array;
}
/* Check whether string macthes any of the pattern in PATARRAY and
returns the matching pattern item or NULL. */
static pattern_t *
match_p (const char *string, pattern_t *patarray)
{
pattern_t *pat;
if (!*string)
{
if (opt.verbose)
log_info ("zero length input line - ignored\n");
return NULL;
}
for (pat = patarray; pat->type != PAT_NULL; pat++)
{
if (pat->type == PAT_STRING)
{
if (!strcasecmp (pat->u.s.string, string))
return pat;
}
else if (pat->type == PAT_REGEX)
{
int rerr;
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
if (!rerr)
return pat;
else if (rerr != REG_NOMATCH)
{
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
log_error ("matching r.e. failed: %s\n", rerrbuf);
xfree (rerrbuf);
return pat; /* Better indicate a match on error. */
}
}
else
BUG ();
}
return NULL;
}
/* Actual processing of the input. This function does not return an
error code but exits as soon as a match has been found. */
static void
process (FILE *fp, pattern_t *patarray)
{
char buffer[2048];
size_t idx;
int c;
unsigned long lineno = 0;
pattern_t *pat;
idx = 0;
c = 0;
while (idx < sizeof buffer -1 && c != EOF )
{
if ((c = getc (fp)) != EOF)
buffer[idx] = c;
if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
{
lineno++;
if (!opt.null)
{
while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
idx--;
}
buffer[idx] = 0;
pat = match_p (buffer, patarray);
if (pat)
{
if (opt.verbose)
log_error ("input line %lu matches pattern at line %u"
" - rejected\n",
lineno, pat->lineno);
exit (1);
}
idx = 0;
}
else
idx++;
}
if (c != EOF)
{
log_error ("input line %lu too long - rejected\n", lineno+1);
exit (1);
}
if (ferror (fp))
{
log_error ("input read error at line %lu: %s - rejected\n",
lineno+1, strerror (errno));
exit (1);
}
if (opt.verbose)
log_info ("no input line matches the pattern - accepted\n");
}
diff --git a/tools/gpg-connect-agent.c b/tools/gpg-connect-agent.c
index 0a128b31a..564b3007f 100644
--- a/tools/gpg-connect-agent.c
+++ b/tools/gpg-connect-agent.c
@@ -1,2281 +1,2281 @@
/* gpg-connect-agent.c - Tool to connect to the agent.
* Copyright (C) 2005, 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <assuan.h>
#include <unistd.h>
#include <assert.h>
#include "../common/i18n.h"
#include "../common/util.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "../common/membuf.h"
#include "../common/ttyio.h"
#ifdef HAVE_W32_SYSTEM
# include "../common/exechelp.h"
#endif
#include "../common/init.h"
#define CONTROL_D ('D' - 'A' + 1)
#define octdigitp(p) (*(p) >= '0' && *(p) <= '7')
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRawSocket = 'S',
oTcpSocket = 'T',
oExec = 'E',
oRun = 'r',
oSubst = 's',
oNoVerbose = 500,
oHomedir,
oAgentProgram,
oDirmngrProgram,
oKeyboxdProgram,
oHex,
oDecode,
oNoExtConnect,
oDirmngr,
oKeyboxd,
oUIServer,
oNoAutostart
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")),
ARGPARSE_s_n (oHex, "hex", N_("print data out hex encoded")),
ARGPARSE_s_n (oDecode,"decode", N_("decode received data lines")),
ARGPARSE_s_n (oDirmngr,"dirmngr", N_("connect to the dirmngr")),
ARGPARSE_s_n (oKeyboxd,"keyboxd", N_("connect to the keyboxd")),
ARGPARSE_s_n (oUIServer, "uiserver", "@"),
ARGPARSE_s_s (oRawSocket, "raw-socket",
N_("|NAME|connect to Assuan socket NAME")),
ARGPARSE_s_s (oTcpSocket, "tcp-socket",
N_("|ADDR|connect to Assuan server at ADDR")),
ARGPARSE_s_n (oExec, "exec",
N_("run the Assuan server given on the command line")),
ARGPARSE_s_n (oNoExtConnect, "no-ext-connect",
N_("do not use extended connect mode")),
ARGPARSE_s_s (oRun, "run",
N_("|FILE|run commands from FILE on startup")),
ARGPARSE_s_n (oSubst, "subst", N_("run /subst on startup")),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@" ),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"),
ARGPARSE_end ()
};
/* We keep all global options in the structure OPT. */
struct
{
int verbose; /* Verbosity level. */
int quiet; /* Be extra quiet. */
int autostart; /* Start the server if not running. */
const char *homedir; /* Configuration directory name */
const char *agent_program; /* Value of --agent-program. */
const char *dirmngr_program; /* Value of --dirmngr-program. */
const char *keyboxd_program; /* Value of --keyboxd-program. */
int hex; /* Print data lines in hex format. */
int decode; /* Decode received data lines. */
int use_dirmngr; /* Use the dirmngr and not gpg-agent. */
int use_keyboxd; /* Use the keyboxd and not gpg-agent. */
int use_uiserver; /* Use the standard UI server. */
const char *raw_socket; /* Name of socket to connect in raw mode. */
const char *tcp_socket; /* Name of server to connect in tcp mode. */
int exec; /* Run the pgm given on the command line. */
unsigned int connect_flags; /* Flags used for connecting. */
int enable_varsubst; /* Set if variable substitution is enabled. */
int trim_leading_spaces;
} opt;
/* Definitions for /definq commands and a global linked list with all
the definitions. */
struct definq_s
{
struct definq_s *next;
char *name; /* Name of inquiry or NULL for any name. */
int is_var; /* True if FILE is a variable name. */
int is_prog; /* True if FILE is a program to run. */
char file[1]; /* Name of file or program. */
};
typedef struct definq_s *definq_t;
static definq_t definq_list;
static definq_t *definq_list_tail = &definq_list;
/* Variable definitions and glovbal table. */
struct variable_s
{
struct variable_s *next;
char *value; /* Malloced value - always a string. */
char name[1]; /* Name of the variable. */
};
typedef struct variable_s *variable_t;
static variable_t variable_table;
/* To implement loops we store entire lines in a linked list. */
struct loopline_s
{
struct loopline_s *next;
char line[1];
};
typedef struct loopline_s *loopline_t;
/* This is used to store the pid of the server. */
static pid_t server_pid = (pid_t)(-1);
/* The current datasink file or NULL. */
static FILE *current_datasink;
/* A list of open file descriptors. */
static struct
{
int inuse;
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#endif
} open_fd_table[256];
/*-- local prototypes --*/
static char *substitute_line_copy (const char *buffer);
static int read_and_print_response (assuan_context_t ctx, int withhash,
int *r_goterr);
static assuan_context_t start_agent (void);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPG@-connect-agent (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPG@-connect-agent [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPG@-connect-agent [options]\n"
"Connect to a running agent and send commands\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
case 33: p = "\n"; break;
default: p = NULL; break;
}
return p;
}
/* Unescape STRING and returned the malloced result. The surrounding
quotes must already be removed from STRING. */
static char *
unescape_string (const char *string)
{
const unsigned char *s;
int esc;
size_t n;
char *buffer;
unsigned char *d;
n = 0;
for (s = (const unsigned char*)string, esc=0; *s; s++)
{
if (esc)
{
switch (*s)
{
case 'b':
case 't':
case 'v':
case 'n':
case 'f':
case 'r':
case '"':
case '\'':
case '\\': n++; break;
case 'x':
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
n++;
break;
default:
if (s[1] && s[2]
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
n++;
break;
}
esc = 0;
}
else if (*s == '\\')
esc = 1;
else
n++;
}
buffer = xmalloc (n+1);
d = (unsigned char*)buffer;
for (s = (const unsigned char*)string, esc=0; *s; s++)
{
if (esc)
{
switch (*s)
{
case 'b': *d++ = '\b'; break;
case 't': *d++ = '\t'; break;
case 'v': *d++ = '\v'; break;
case 'n': *d++ = '\n'; break;
case 'f': *d++ = '\f'; break;
case 'r': *d++ = '\r'; break;
case '"': *d++ = '\"'; break;
case '\'': *d++ = '\''; break;
case '\\': *d++ = '\\'; break;
case 'x':
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
{
s++;
*d++ = xtoi_2 (s);
s++;
}
break;
default:
if (s[1] && s[2]
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
{
*d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2);
s += 2;
}
break;
}
esc = 0;
}
else if (*s == '\\')
esc = 1;
else
*d++ = *s;
}
*d = 0;
return buffer;
}
/* Do the percent unescaping and return a newly malloced string.
If WITH_PLUS is set '+' characters will be changed to space. */
static char *
unpercent_string (const char *string, int with_plus)
{
const unsigned char *s;
unsigned char *buffer, *p;
size_t n;
n = 0;
for (s=(const unsigned char *)string; *s; s++)
{
if (*s == '%' && s[1] && s[2])
{
s++;
n++;
s++;
}
else if (with_plus && *s == '+')
n++;
else
n++;
}
buffer = xmalloc (n+1);
p = buffer;
for (s=(const unsigned char *)string; *s; s++)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*p++ = xtoi_2 (s);
s++;
}
else if (with_plus && *s == '+')
*p++ = ' ';
else
*p++ = *s;
}
*p = 0;
return (char*)buffer;
}
static const char *
set_var (const char *name, const char *value)
{
variable_t var;
for (var = variable_table; var; var = var->next)
if (!strcmp (var->name, name))
break;
if (!var)
{
var = xmalloc (sizeof *var + strlen (name));
var->value = NULL;
strcpy (var->name, name);
var->next = variable_table;
variable_table = var;
}
xfree (var->value);
var->value = value? xstrdup (value) : NULL;
return var->value;
}
static void
set_int_var (const char *name, int value)
{
char numbuf[35];
snprintf (numbuf, sizeof numbuf, "%d", value);
set_var (name, numbuf);
}
/* Return the value of a variable. That value is valid until a
variable of the name is changed. Return NULL if not found. Note
that envvars are copied to our variable list at the first access
and not at oprogram start. */
static const char *
get_var (const char *name)
{
variable_t var;
const char *s;
if (!*name)
return "";
for (var = variable_table; var; var = var->next)
if (!strcmp (var->name, name))
break;
if (!var && (s = getenv (name)))
return set_var (name, s);
if (!var || !var->value)
return NULL;
return var->value;
}
/* Perform some simple arithmetic operations. Caller must release
the return value. On error the return value is NULL. */
static char *
arithmetic_op (int operator, const char *operands)
{
long result, value;
char numbuf[35];
while ( spacep (operands) )
operands++;
if (!*operands)
return NULL;
result = strtol (operands, NULL, 0);
while (*operands && !spacep (operands) )
operands++;
if (operator == '!')
result = !result;
while (*operands)
{
while ( spacep (operands) )
operands++;
if (!*operands)
break;
value = strtol (operands, NULL, 0);
while (*operands && !spacep (operands) )
operands++;
switch (operator)
{
case '+': result += value; break;
case '-': result -= value; break;
case '*': result *= value; break;
case '/':
if (!value)
return NULL;
result /= value;
break;
case '%':
if (!value)
return NULL;
result %= value;
break;
case '!': result = !value; break;
case '|': result = result || value; break;
case '&': result = result && value; break;
default:
log_error ("unknown arithmetic operator '%c'\n", operator);
return NULL;
}
}
snprintf (numbuf, sizeof numbuf, "%ld", result);
return xstrdup (numbuf);
}
/* Extended version of get_var. This returns a malloced string and
understand the function syntax: "func args".
Defined functions are
get - Return a value described by the next argument:
cwd - The current working directory.
homedir - The gnupg homedir.
sysconfdir - GnuPG's system configuration directory.
bindir - GnuPG's binary directory.
libdir - GnuPG's library directory.
libexecdir - GnuPG's library directory for executable files.
datadir - GnuPG's data directory.
serverpid - The PID of the current server.
unescape ARGS
Remove C-style escapes from string. Note that "\0" and
- "\x00" terminate the string implictly. Use "\x7d" to
+ "\x00" terminate the string implicitly. Use "\x7d" to
represent the closing brace. The args start right after
the first space after the function name.
unpercent ARGS
unpercent+ ARGS
Remove percent style ecaping from string. Note that "%00
terminates the string implicitly. Use "%7d" to represetn
the closing brace. The args start right after the first
space after the function name. "unpercent+" also maps '+'
to space.
percent ARGS
percent+ ARGS
Escape the args using the percent style. Tabs, formfeeds,
linefeeds, carriage return, and the plus sign are also
escaped. "percent+" also maps spaces to plus characters.
errcode ARG
Assuming ARG is an integer, return the gpg-error code.
errsource ARG
Assuming ARG is an integer, return the gpg-error source.
errstring ARG
Assuming ARG is an integer return a formatted fpf error string.
Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg"
*/
static char *
get_var_ext (const char *name)
{
static int recursion_count;
const char *s;
char *result;
char *p;
char *free_me = NULL;
int intvalue;
if (recursion_count > 50)
{
log_error ("variables nested too deeply\n");
return NULL;
}
recursion_count++;
free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL;
if (free_me)
name = free_me;
for (s=name; *s && !spacep (s); s++)
;
if (!*s)
{
s = get_var (name);
result = s? xstrdup (s): NULL;
}
else if ( (s - name) == 3 && !strncmp (name, "get", 3))
{
while ( spacep (s) )
s++;
if (!strcmp (s, "cwd"))
{
result = gnupg_getcwd ();
if (!result)
log_error ("getcwd failed: %s\n", strerror (errno));
}
else if (!strcmp (s, "homedir"))
result = xstrdup (gnupg_homedir ());
else if (!strcmp (s, "sysconfdir"))
result = xstrdup (gnupg_sysconfdir ());
else if (!strcmp (s, "bindir"))
result = xstrdup (gnupg_bindir ());
else if (!strcmp (s, "libdir"))
result = xstrdup (gnupg_libdir ());
else if (!strcmp (s, "libexecdir"))
result = xstrdup (gnupg_libexecdir ());
else if (!strcmp (s, "datadir"))
result = xstrdup (gnupg_datadir ());
else if (!strcmp (s, "serverpid"))
result = xasprintf ("%d", (int)server_pid);
else
{
log_error ("invalid argument '%s' for variable function 'get'\n", s);
log_info ("valid are: cwd, "
"{home,bin,lib,libexec,data}dir, serverpid\n");
result = NULL;
}
}
else if ( (s - name) == 8 && !strncmp (name, "unescape", 8))
{
s++;
result = unescape_string (s);
}
else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9))
{
s++;
result = unpercent_string (s, 0);
}
else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10))
{
s++;
result = unpercent_string (s, 1);
}
else if ( (s - name) == 7 && !strncmp (name, "percent", 7))
{
s++;
result = percent_escape (s, "+\t\r\n\f\v");
}
else if ( (s - name) == 8 && !strncmp (name, "percent+", 8))
{
s++;
result = percent_escape (s, "+\t\r\n\f\v");
for (p=result; *p; p++)
if (*p == ' ')
*p = '+';
}
else if ( (s - name) == 7 && !strncmp (name, "errcode", 7))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%d", gpg_err_code (intvalue));
}
else if ( (s - name) == 9 && !strncmp (name, "errsource", 9))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%d", gpg_err_source (intvalue));
}
else if ( (s - name) == 9 && !strncmp (name, "errstring", 9))
{
s++;
intvalue = (int)strtol (s, NULL, 0);
result = xasprintf ("%s <%s>",
gpg_strerror (intvalue), gpg_strsource (intvalue));
}
else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name))
{
result = arithmetic_op (*name, s+1);
}
else
{
log_error ("unknown variable function '%.*s'\n", (int)(s-name), name);
result = NULL;
}
xfree (free_me);
recursion_count--;
return result;
}
/* Substitute variables in LINE and return a new allocated buffer if
required. The function might modify LINE if the expanded version
fits into it. */
static char *
substitute_line (char *buffer)
{
char *line = buffer;
char *p, *pend;
const char *value;
size_t valuelen, n;
char *result = NULL;
char *freeme = NULL;
while (*line)
{
p = strchr (line, '$');
if (!p)
return result; /* No more variables. */
if (p[1] == '$') /* Escaped dollar sign. */
{
memmove (p, p+1, strlen (p+1)+1);
line = p + 1;
continue;
}
if (p[1] == '{')
{
int count = 0;
for (pend=p+2; *pend; pend++)
{
if (*pend == '{')
count++;
else if (*pend == '}')
{
if (--count < 0)
break;
}
}
if (!*pend)
return result; /* Unclosed - don't substitute. */
}
else
{
for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++)
;
}
if (p[1] == '{' && *pend == '}')
{
int save = *pend;
*pend = 0;
freeme = get_var_ext (p+2);
value = freeme;
*pend++ = save;
}
else if (*pend)
{
int save = *pend;
*pend = 0;
value = get_var (p+1);
*pend = save;
}
else
value = get_var (p+1);
if (!value)
value = "";
valuelen = strlen (value);
if (valuelen <= pend - p)
{
memcpy (p, value, valuelen);
p += valuelen;
n = pend - p;
if (n)
memmove (p, p+n, strlen (p+n)+1);
line = p;
}
else
{
char *src = result? result : buffer;
char *dst;
dst = xmalloc (strlen (src) + valuelen + 1);
n = p - src;
memcpy (dst, src, n);
memcpy (dst + n, value, valuelen);
n += valuelen;
strcpy (dst + n, pend);
line = dst + n;
xfree (result);
result = dst;
}
xfree (freeme);
freeme = NULL;
}
return result;
}
/* Same as substitute_line but do not modify BUFFER. */
static char *
substitute_line_copy (const char *buffer)
{
char *result, *p;
p = xstrdup (buffer?buffer:"");
result = substitute_line (p);
if (!result)
result = p;
else
xfree (p);
return result;
}
static void
assign_variable (char *line, int syslet)
{
char *name, *p, *tmp, *free_me, *buffer;
/* Get the name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
if (!*p)
set_var (name, NULL); /* Remove variable. */
else if (syslet)
{
free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL;
if (free_me)
p = free_me;
buffer = xmalloc (4 + strlen (p) + 1);
strcpy (stpcpy (buffer, "get "), p);
tmp = get_var_ext (buffer);
xfree (buffer);
set_var (name, tmp);
xfree (tmp);
xfree (free_me);
}
else
{
tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL;
if (tmp)
{
set_var (name, tmp);
xfree (tmp);
}
else
set_var (name, p);
}
}
static void
show_variables (void)
{
variable_t var;
for (var = variable_table; var; var = var->next)
if (var->value)
printf ("%-20s %s\n", var->name, var->value);
}
/* Store an inquire response pattern. Note, that this function may
change the content of LINE. We assume that leading white spaces
are already removed. */
static void
add_definq (char *line, int is_var, int is_prog)
{
definq_t d;
char *name, *p;
/* Get name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
d = xmalloc (sizeof *d + strlen (p) );
strcpy (d->file, p);
d->is_var = is_var;
d->is_prog = is_prog;
if ( !strcmp (name, "*"))
d->name = NULL;
else
d->name = xstrdup (name);
d->next = NULL;
*definq_list_tail = d;
definq_list_tail = &d->next;
}
/* Show all inquiry definitions. */
static void
show_definq (void)
{
definq_t d;
for (d=definq_list; d; d = d->next)
if (d->name)
printf ("%-20s %c %s\n",
d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file);
for (d=definq_list; d; d = d->next)
if (!d->name)
printf ("%-20s %c %s\n", "*",
d->is_var? 'v': d->is_prog? 'p':'f', d->file);
}
/* Clear all inquiry definitions. */
static void
clear_definq (void)
{
while (definq_list)
{
definq_t tmp = definq_list->next;
xfree (definq_list->name);
xfree (definq_list);
definq_list = tmp;
}
definq_list_tail = &definq_list;
}
static void
do_sendfd (assuan_context_t ctx, char *line)
{
FILE *fp;
char *name, *mode, *p;
int rc, fd;
/* Get file name. */
name = line;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get mode. */
mode = p;
if (!*mode)
mode = "r";
else
{
for (p=mode; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
}
/* Open and send. */
fp = fopen (name, mode);
if (!fp)
{
log_error ("can't open '%s' in \"%s\" mode: %s\n",
name, mode, strerror (errno));
return;
}
fd = fileno (fp);
if (opt.verbose)
log_error ("file '%s' opened in \"%s\" mode, fd=%d\n",
name, mode, fd);
rc = assuan_sendfd (ctx, INT2FD (fd) );
if (rc)
log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc));
fclose (fp);
}
static void
do_recvfd (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
log_info ("This command has not yet been implemented\n");
}
static void
do_open (char *line)
{
FILE *fp;
char *varname, *name, *mode, *p;
int fd;
#ifdef HAVE_W32_SYSTEM
if (server_pid == (pid_t)(-1))
{
log_error ("the pid of the server is unknown\n");
log_info ("use command \"/serverpid\" first\n");
return;
}
#endif
/* Get variable name. */
varname = line;
for (p=varname; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get file name. */
name = p;
for (p=name; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
/* Get mode. */
mode = p;
if (!*mode)
mode = "r";
else
{
for (p=mode; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
}
/* Open and send. */
fp = fopen (name, mode);
if (!fp)
{
log_error ("can't open '%s' in \"%s\" mode: %s\n",
name, mode, strerror (errno));
return;
}
fd = dup (fileno (fp));
if (fd >= 0 && fd < DIM (open_fd_table))
{
open_fd_table[fd].inuse = 1;
#ifdef HAVE_W32CE_SYSTEM
# warning fixme: implement our pipe emulation.
#endif
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
{
HANDLE prochandle, handle, newhandle;
handle = (void*)_get_osfhandle (fd);
prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid);
if (!prochandle)
{
log_error ("failed to open the server process\n");
close (fd);
return;
}
if (!DuplicateHandle (GetCurrentProcess(), handle,
prochandle, &newhandle, 0,
TRUE, DUPLICATE_SAME_ACCESS ))
{
log_error ("failed to duplicate the handle\n");
close (fd);
CloseHandle (prochandle);
return;
}
CloseHandle (prochandle);
open_fd_table[fd].handle = newhandle;
}
if (opt.verbose)
log_info ("file '%s' opened in \"%s\" mode, fd=%d (libc=%d)\n",
name, mode, (int)open_fd_table[fd].handle, fd);
set_int_var (varname, (int)open_fd_table[fd].handle);
#else
if (opt.verbose)
log_info ("file '%s' opened in \"%s\" mode, fd=%d\n",
name, mode, fd);
set_int_var (varname, fd);
#endif
}
else
{
log_error ("can't put fd %d into table\n", fd);
if (fd != -1)
close (fd); /* Table was full. */
}
fclose (fp);
}
static void
do_close (char *line)
{
int fd = atoi (line);
#ifdef HAVE_W32_SYSTEM
int i;
for (i=0; i < DIM (open_fd_table); i++)
if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd)
break;
if (i < DIM (open_fd_table))
fd = i;
else
{
log_error ("given fd (system handle) has not been opened\n");
return;
}
#endif
if (fd < 0 || fd >= DIM (open_fd_table))
{
log_error ("invalid fd\n");
return;
}
if (!open_fd_table[fd].inuse)
{
log_error ("given fd has not been opened\n");
return;
}
#ifdef HAVE_W32_SYSTEM
CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */
#endif
close (fd);
open_fd_table[fd].inuse = 0;
}
static void
do_showopen (void)
{
int i;
for (i=0; i < DIM (open_fd_table); i++)
if (open_fd_table[i].inuse)
{
#ifdef HAVE_W32_SYSTEM
printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i);
#else
printf ("%-15d\n", i);
#endif
}
}
static gpg_error_t
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* Get the pid of the server and store it locally. */
static void
do_serverpid (assuan_context_t ctx)
{
int rc;
membuf_t mb;
char *buffer;
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
log_error ("command \"%s\" failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
else
{
server_pid = (pid_t)strtoul (buffer, NULL, 10);
if (opt.verbose)
log_info ("server's PID is %lu\n", (unsigned long)server_pid);
}
xfree (buffer);
}
/* Return true if the command is either "HELP" or "SCD HELP". */
static int
help_cmd_p (const char *line)
{
if (!ascii_strncasecmp (line, "SCD", 3)
&& (spacep (line+3) || !line[3]))
{
for (line += 3; spacep (line); line++)
;
}
return (!ascii_strncasecmp (line, "HELP", 4)
&& (spacep (line+4) || !line[4]));
}
/* gpg-connect-agent's entry point. */
int
main (int argc, char **argv)
{
ARGPARSE_ARGS pargs;
int no_more_options = 0;
assuan_context_t ctx;
char *line, *p;
char *tmpline;
size_t linesize;
int rc;
int cmderr;
const char *opt_run = NULL;
gpgrt_stream_t script_fp = NULL;
int use_tty, keep_line;
struct {
int collecting;
loopline_t head;
loopline_t *tail;
loopline_t current;
unsigned int nestlevel;
int oneshot;
char *condition;
} loopstack[20];
int loopidx;
char **cmdline_commands = NULL;
early_system_init ();
gnupg_rl_initialize ();
set_strusage (my_strusage);
log_set_prefix ("gpg-connect-agent", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (0);
opt.autostart = 1;
opt.connect_flags = 1;
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break;
case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break;
case oNoAutostart: opt.autostart = 0; break;
case oHex: opt.hex = 1; break;
case oDecode: opt.decode = 1; break;
case oDirmngr: opt.use_dirmngr = 1; break;
case oKeyboxd: opt.use_keyboxd = 1; break;
case oUIServer: opt.use_uiserver = 1; break;
case oRawSocket: opt.raw_socket = pargs.r.ret_str; break;
case oTcpSocket: opt.tcp_socket = pargs.r.ret_str; break;
case oExec: opt.exec = 1; break;
case oNoExtConnect: opt.connect_flags &= ~(1); break;
case oRun: opt_run = pargs.r.ret_str; break;
case oSubst:
opt.enable_varsubst = 1;
opt.trim_leading_spaces = 1;
break;
default: pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
exit (2);
/* --uiserver is a shortcut for a specific raw socket. This comes
in particular handy on Windows. */
if (opt.use_uiserver)
{
opt.raw_socket = make_absfilename (gnupg_homedir (), "S.uiserver", NULL);
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
use_tty = (gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)));
if (opt.exec)
{
if (!argc)
{
log_error (_("option \"%s\" requires a program "
"and optional arguments\n"), "--exec" );
exit (1);
}
}
else if (argc)
cmdline_commands = argv;
if (opt.exec && opt.raw_socket)
{
opt.raw_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--raw-socket", "--exec");
}
if (opt.exec && opt.tcp_socket)
{
opt.tcp_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--tcp-socket", "--exec");
}
if (opt.tcp_socket && opt.raw_socket)
{
opt.tcp_socket = NULL;
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
"--tcp-socket", "--raw-socket");
}
if (opt_run && !(script_fp = gpgrt_fopen (opt_run, "r")))
{
log_error ("cannot open run file '%s': %s\n",
opt_run, strerror (errno));
exit (1);
}
if (opt.exec)
{
assuan_fd_t no_close[3];
no_close[0] = assuan_fd_from_posix_fd (es_fileno (es_stderr));
no_close[1] = assuan_fd_from_posix_fd (log_get_fd ());
no_close[2] = ASSUAN_INVALID_FD;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_pipe_connect
(ctx, *argv, (const char **)argv, no_close, NULL, NULL,
(opt.connect_flags & 1) ? ASSUAN_PIPE_CONNECT_FDPASSING : 0);
if (rc)
{
log_error ("assuan_pipe_connect_ext failed: %s\n",
gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("server '%s' started\n", *argv);
}
else if (opt.raw_socket)
{
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_socket_connect
(ctx, opt.raw_socket, 0,
(opt.connect_flags & 1) ? ASSUAN_SOCKET_CONNECT_FDPASSING : 0);
if (rc)
{
log_error ("can't connect to socket '%s': %s\n",
opt.raw_socket, gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("connection to socket '%s' established\n", opt.raw_socket);
}
else if (opt.tcp_socket)
{
char *url;
url = xstrconcat ("assuan://", opt.tcp_socket, NULL);
rc = assuan_new (&ctx);
if (rc)
{
log_error ("assuan_new failed: %s\n", gpg_strerror (rc));
exit (1);
}
rc = assuan_socket_connect (ctx, opt.tcp_socket, 0, 0);
if (rc)
{
log_error ("can't connect to server '%s': %s\n",
opt.tcp_socket, gpg_strerror (rc));
exit (1);
}
if (opt.verbose)
log_info ("connection to socket '%s' established\n", url);
xfree (url);
}
else
ctx = start_agent ();
/* See whether there is a line pending from the server (in case
assuan did not run the initial handshaking). */
if (assuan_pending_line (ctx))
{
rc = read_and_print_response (ctx, 0, &cmderr);
if (rc)
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
}
for (loopidx=0; loopidx < DIM (loopstack); loopidx++)
loopstack[loopidx].collecting = 0;
loopidx = -1;
line = NULL;
linesize = 0;
keep_line = 1;
for (;;)
{
int n;
size_t maxlength = 2048;
assert (loopidx < (int)DIM (loopstack));
if (loopidx >= 0 && loopstack[loopidx].current)
{
keep_line = 0;
xfree (line);
line = xstrdup (loopstack[loopidx].current->line);
n = strlen (line);
/* Never go beyond of the final /end. */
if (loopstack[loopidx].current->next)
loopstack[loopidx].current = loopstack[loopidx].current->next;
else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
;
else
log_fatal ("/end command vanished\n");
}
else if (cmdline_commands && *cmdline_commands && !script_fp)
{
keep_line = 0;
xfree (line);
line = xstrdup (*cmdline_commands);
cmdline_commands++;
n = strlen (line);
if (n >= maxlength)
maxlength = 0;
}
else if (use_tty && !script_fp)
{
keep_line = 0;
xfree (line);
line = tty_get ("> ");
n = strlen (line);
if (n==1 && *line == CONTROL_D)
n = 0;
if (n >= maxlength)
maxlength = 0;
}
else
{
if (!keep_line)
{
xfree (line);
line = NULL;
linesize = 0;
keep_line = 1;
}
n = gpgrt_read_line (script_fp ? script_fp : gpgrt_stdin,
&line, &linesize, &maxlength);
}
if (n < 0)
{
log_error (_("error reading input: %s\n"), strerror (errno));
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
log_error ("stopping script execution\n");
continue;
}
exit (1);
}
if (!n)
{
/* EOF */
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
if (opt.verbose)
log_info ("end of script\n");
continue;
}
break;
}
if (!maxlength)
{
log_error (_("line too long - skipped\n"));
continue;
}
if (memchr (line, 0, n))
log_info (_("line shortened due to embedded Nul character\n"));
if (line[n-1] == '\n')
line[n-1] = 0;
if (opt.trim_leading_spaces)
{
const char *s = line;
while (spacep (s))
s++;
if (s != line)
{
for (p=line; *s;)
*p++ = *s++;
*p = 0;
n = p - line;
}
}
if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting)
{
loopline_t ll;
ll = xmalloc (sizeof *ll + strlen (line));
ll->next = NULL;
strcpy (ll->line, line);
*loopstack[loopidx+1].tail = ll;
loopstack[loopidx+1].tail = &ll->next;
if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
loopstack[loopidx+1].nestlevel--;
else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6)))
loopstack[loopidx+1].nestlevel++;
if (loopstack[loopidx+1].nestlevel)
continue;
/* We reached the corresponding /end. */
loopstack[loopidx+1].collecting = 0;
loopidx++;
}
if (*line == '/')
{
/* Handle control commands. */
char *cmd = line+1;
for (p=cmd; *p && !spacep (p); p++)
;
if (*p)
*p++ = 0;
while (spacep (p))
p++;
if (!strcmp (cmd, "let"))
{
assign_variable (p, 0);
}
else if (!strcmp (cmd, "slet"))
{
/* Deprecated - never used in a released version. */
assign_variable (p, 1);
}
else if (!strcmp (cmd, "showvar"))
{
show_variables ();
}
else if (!strcmp (cmd, "definq"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 1, 0);
xfree (tmpline);
}
else
add_definq (p, 1, 0);
}
else if (!strcmp (cmd, "definqfile"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 0, 0);
xfree (tmpline);
}
else
add_definq (p, 0, 0);
}
else if (!strcmp (cmd, "definqprog"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
add_definq (tmpline, 0, 1);
xfree (tmpline);
}
else
add_definq (p, 0, 1);
}
else if (!strcmp (cmd, "datafile"))
{
const char *fname;
if (current_datasink)
{
if (current_datasink != stdout)
fclose (current_datasink);
current_datasink = NULL;
}
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
fname = tmpline? tmpline : p;
if (fname && !strcmp (fname, "-"))
current_datasink = stdout;
else if (fname && *fname)
{
current_datasink = fopen (fname, "wb");
if (!current_datasink)
log_error ("can't open '%s': %s\n",
fname, strerror (errno));
}
xfree (tmpline);
}
else if (!strcmp (cmd, "showdef"))
{
show_definq ();
}
else if (!strcmp (cmd, "cleardef"))
{
clear_definq ();
}
else if (!strcmp (cmd, "echo"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
puts (tmpline);
xfree (tmpline);
}
else
puts (p);
}
else if (!strcmp (cmd, "sendfd"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_sendfd (ctx, tmpline);
xfree (tmpline);
}
else
do_sendfd (ctx, p);
continue;
}
else if (!strcmp (cmd, "recvfd"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_recvfd (ctx, tmpline);
xfree (tmpline);
}
else
do_recvfd (ctx, p);
continue;
}
else if (!strcmp (cmd, "open"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_open (tmpline);
xfree (tmpline);
}
else
do_open (p);
}
else if (!strcmp (cmd, "close"))
{
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
if (tmpline)
{
do_close (tmpline);
xfree (tmpline);
}
else
do_close (p);
}
else if (!strcmp (cmd, "showopen"))
{
do_showopen ();
}
else if (!strcmp (cmd, "serverpid"))
{
do_serverpid (ctx);
}
else if (!strcmp (cmd, "hex"))
opt.hex = 1;
else if (!strcmp (cmd, "nohex"))
opt.hex = 0;
else if (!strcmp (cmd, "decode"))
opt.decode = 1;
else if (!strcmp (cmd, "nodecode"))
opt.decode = 0;
else if (!strcmp (cmd, "subst"))
{
opt.enable_varsubst = 1;
opt.trim_leading_spaces = 1;
}
else if (!strcmp (cmd, "nosubst"))
opt.enable_varsubst = 0;
else if (!strcmp (cmd, "run"))
{
char *p2;
for (p2=p; *p2 && !spacep (p2); p2++)
;
if (*p2)
*p2++ = 0;
while (spacep (p2))
p++;
if (*p2)
{
log_error ("syntax error in run command\n");
if (script_fp)
{
gpgrt_fclose (script_fp);
script_fp = NULL;
}
}
else if (script_fp)
{
log_error ("cannot nest run commands - stop\n");
gpgrt_fclose (script_fp);
script_fp = NULL;
}
else if (!(script_fp = gpgrt_fopen (p, "r")))
{
log_error ("cannot open run file '%s': %s\n",
p, strerror (errno));
}
else if (opt.verbose)
log_info ("running commands from '%s'\n", p);
}
else if (!strcmp (cmd, "while"))
{
if (loopidx+2 >= (int)DIM(loopstack))
{
log_error ("blocks are nested too deep\n");
/* We should better die or break all loop in this
case as recovering from this error won't be
easy. */
}
else
{
loopstack[loopidx+1].head = NULL;
loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
loopstack[loopidx+1].current = NULL;
loopstack[loopidx+1].nestlevel = 1;
loopstack[loopidx+1].oneshot = 0;
loopstack[loopidx+1].condition = xstrdup (p);
loopstack[loopidx+1].collecting = 1;
}
}
else if (!strcmp (cmd, "if"))
{
if (loopidx+2 >= (int)DIM(loopstack))
{
log_error ("blocks are nested too deep\n");
}
else
{
/* Note that we need to evaluate the condition right
away and not just at the end of the block as we
do with a WHILE. */
loopstack[loopidx+1].head = NULL;
loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
loopstack[loopidx+1].current = NULL;
loopstack[loopidx+1].nestlevel = 1;
loopstack[loopidx+1].oneshot = 1;
loopstack[loopidx+1].condition = substitute_line_copy (p);
loopstack[loopidx+1].collecting = 1;
}
}
else if (!strcmp (cmd, "end"))
{
if (loopidx < 0)
log_error ("stray /end command encountered - ignored\n");
else
{
char *tmpcond;
const char *value;
long condition;
/* Evaluate the condition. */
tmpcond = xstrdup (loopstack[loopidx].condition);
if (loopstack[loopidx].oneshot)
{
xfree (loopstack[loopidx].condition);
loopstack[loopidx].condition = xstrdup ("0");
}
tmpline = substitute_line (tmpcond);
value = tmpline? tmpline : tmpcond;
/* "true" or "yes" are commonly used to mean TRUE;
all other strings will evaluate to FALSE due to
the strtoul. */
if (!ascii_strcasecmp (value, "true")
|| !ascii_strcasecmp (value, "yes"))
condition = 1;
else
condition = strtol (value, NULL, 0);
xfree (tmpline);
xfree (tmpcond);
if (condition)
{
/* Run loop. */
loopstack[loopidx].current = loopstack[loopidx].head;
}
else
{
/* Cleanup. */
while (loopstack[loopidx].head)
{
loopline_t tmp = loopstack[loopidx].head->next;
xfree (loopstack[loopidx].head);
loopstack[loopidx].head = tmp;
}
loopstack[loopidx].tail = NULL;
loopstack[loopidx].current = NULL;
loopstack[loopidx].nestlevel = 0;
loopstack[loopidx].collecting = 0;
loopstack[loopidx].oneshot = 0;
xfree (loopstack[loopidx].condition);
loopstack[loopidx].condition = NULL;
loopidx--;
}
}
}
else if (!strcmp (cmd, "bye"))
{
break;
}
else if (!strcmp (cmd, "sleep"))
{
gnupg_sleep (1);
}
else if (!strcmp (cmd, "help"))
{
puts (
"Available commands:\n"
"/echo ARGS Echo ARGS.\n"
"/let NAME VALUE Set variable NAME to VALUE.\n"
"/showvar Show all variables.\n"
"/definq NAME VAR Use content of VAR for inquiries with NAME.\n"
"/definqfile NAME FILE Use content of FILE for inquiries with NAME.\n"
"/definqprog NAME PGM Run PGM for inquiries with NAME.\n"
"/datafile [NAME] Write all D line content to file NAME.\n"
"/showdef Print all definitions.\n"
"/cleardef Delete all definitions.\n"
"/sendfd FILE MODE Open FILE and pass descriptor to server.\n"
"/recvfd Receive FD from server and print.\n"
"/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n"
"/close FD Close file with descriptor FD.\n"
"/showopen Show descriptors of all open files.\n"
"/serverpid Retrieve the pid of the server.\n"
"/[no]hex Enable hex dumping of received data lines.\n"
"/[no]decode Enable decoding of received data lines.\n"
"/[no]subst Enable variable substitution.\n"
"/run FILE Run commands from FILE.\n"
"/if VAR Begin conditional block controlled by VAR.\n"
"/while VAR Begin loop controlled by VAR.\n"
"/end End loop or condition\n"
"/bye Terminate gpg-connect-agent.\n"
"/help Print this help.");
}
else
log_error (_("unknown command '%s'\n"), cmd );
continue;
}
if (opt.verbose && script_fp)
puts (line);
tmpline = opt.enable_varsubst? substitute_line (line) : NULL;
if (tmpline)
{
rc = assuan_write_line (ctx, tmpline);
xfree (tmpline);
}
else
rc = assuan_write_line (ctx, line);
if (rc)
{
log_info (_("sending line failed: %s\n"), gpg_strerror (rc) );
break;
}
if (*line == '#' || !*line)
continue; /* Don't expect a response for a comment line. */
rc = read_and_print_response (ctx, help_cmd_p (line), &cmderr);
if (rc)
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
if ((rc || cmderr) && script_fp)
{
log_error ("stopping script execution\n");
gpgrt_fclose (script_fp);
script_fp = NULL;
}
/* FIXME: If the last command was BYE or the server died for
some other reason, we won't notice until we get the next
input command. Probing the connection with a non-blocking
read could help to notice termination or other problems
early. */
}
if (opt.verbose)
log_info ("closing connection to %s\n",
opt.use_dirmngr? "dirmngr" :
opt.use_keyboxd? "keyboxd" :
"agent");
/* XXX: We would like to release the context here, but libassuan
nicely says good bye to the server, which results in a SIGPIPE if
the server died. Unfortunately, libassuan does not ignore
SIGPIPE when used with UNIX sockets, hence we simply leak the
context here. */
if (0)
assuan_release (ctx);
else
gpgrt_annotate_leaked_object (ctx);
xfree (line);
return 0;
}
/* Handle an Inquire from the server. Return False if it could not be
handled; in this case the caller shll complete the operation. LINE
is the complete line as received from the server. This function
may change the content of LINE. */
static int
handle_inquire (assuan_context_t ctx, char *line)
{
const char *name;
definq_t d;
FILE *fp = NULL;
char buffer[1024];
int rc, n;
/* Skip the command and trailing spaces. */
for (; *line && !spacep (line); line++)
;
while (spacep (line))
line++;
/* Get the name. */
name = line;
for (; *line && !spacep (line); line++)
;
if (*line)
*line++ = 0;
/* Now match it against our list. The second loop is there to
detect the match-all entry. */
for (d=definq_list; d; d = d->next)
if (d->name && !strcmp (d->name, name))
break;
if (!d)
for (d=definq_list; d; d = d->next)
if (!d->name)
break;
if (!d)
{
if (opt.verbose)
log_info ("no handler for inquiry '%s' found\n", name);
return 0;
}
if (d->is_var)
{
char *tmpvalue = get_var_ext (d->file);
if (tmpvalue)
rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue));
else
rc = assuan_send_data (ctx, "", 0);
xfree (tmpvalue);
if (rc)
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
}
else
{
if (d->is_prog)
{
#ifdef HAVE_W32CE_SYSTEM
fp = NULL;
#else
fp = popen (d->file, "r");
#endif
if (!fp)
log_error ("error executing '%s': %s\n",
d->file, strerror (errno));
else if (opt.verbose)
log_error ("handling inquiry '%s' by running '%s'\n",
name, d->file);
}
else
{
fp = fopen (d->file, "rb");
if (!fp)
log_error ("error opening '%s': %s\n", d->file, strerror (errno));
else if (opt.verbose)
log_error ("handling inquiry '%s' by returning content of '%s'\n",
name, d->file);
}
if (!fp)
return 0;
while ( (n = fread (buffer, 1, sizeof buffer, fp)) )
{
rc = assuan_send_data (ctx, buffer, n);
if (rc)
{
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
break;
}
}
if (ferror (fp))
log_error ("error reading from '%s': %s\n", d->file, strerror (errno));
}
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
if (d->is_var)
;
else if (d->is_prog)
{
#ifndef HAVE_W32CE_SYSTEM
if (pclose (fp))
log_error ("error running '%s': %s\n", d->file, strerror (errno));
#endif
}
else
fclose (fp);
return 1;
}
/* Read all response lines from server and print them. Returns 0 on
success or an assuan error code. If WITHHASH istrue, comment lines
are printed. Sets R_GOTERR to true if the command did not returned
OK. */
static int
read_and_print_response (assuan_context_t ctx, int withhash, int *r_goterr)
{
char *line;
size_t linelen;
gpg_error_t rc;
int i, j;
int need_lf = 0;
*r_goterr = 0;
for (;;)
{
do
{
rc = assuan_read_line (ctx, &line, &linelen);
if (rc)
return rc;
if ((withhash || opt.verbose > 1) && *line == '#')
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
while (*line == '#' || !linelen);
if (linelen >= 1
&& line[0] == 'D' && line[1] == ' ')
{
if (current_datasink)
{
const unsigned char *s;
int c = 0;
for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
{
if (*s == '%' && j+2 < linelen)
{
s++; j++;
c = xtoi_2 ( s );
s++; j++;
}
else
c = *s;
putc (c, current_datasink);
}
}
else if (opt.hex)
{
for (i=2; i < linelen; )
{
int save_i = i;
printf ("D[%04X] ", i-2);
for (j=0; j < 16 ; j++, i++)
{
if (j == 8)
putchar (' ');
if (i < linelen)
printf (" %02X", ((unsigned char*)line)[i]);
else
fputs (" ", stdout);
}
fputs (" ", stdout);
i= save_i;
for (j=0; j < 16; j++, i++)
{
unsigned int c = ((unsigned char*)line)[i];
if ( i >= linelen )
putchar (' ');
else if (isascii (c) && isprint (c) && !iscntrl (c))
putchar (c);
else
putchar ('.');
}
putchar ('\n');
}
}
else if (opt.decode)
{
const unsigned char *s;
int need_d = 1;
int c = 0;
for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
{
if (need_d)
{
fputs ("D ", stdout);
need_d = 0;
}
if (*s == '%' && j+2 < linelen)
{
s++; j++;
c = xtoi_2 ( s );
s++; j++;
}
else
c = *s;
if (c == '\n')
need_d = 1;
putchar (c);
}
need_lf = (c != '\n');
}
else
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
else
{
if (need_lf)
{
if (!current_datasink || current_datasink != stdout)
putchar ('\n');
need_lf = 0;
}
if (linelen >= 1
&& line[0] == 'S'
&& (line[1] == '\0' || line[1] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
}
else if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
set_int_var ("?", 0);
return 0;
}
else if (linelen >= 3
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
&& (line[3] == '\0' || line[3] == ' '))
{
int errval;
errval = strtol (line+3, NULL, 10);
if (!errval)
errval = -1;
set_int_var ("?", errval);
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
*r_goterr = 1;
return 0;
}
else if (linelen >= 7
&& line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
&& line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
&& line[6] == 'E'
&& (line[7] == '\0' || line[7] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
if (!handle_inquire (ctx, line))
assuan_write_line (ctx, "CANCEL");
}
else if (linelen >= 3
&& line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
&& (line[3] == '\0' || line[3] == ' '))
{
if (!current_datasink || current_datasink != stdout)
{
fwrite (line, linelen, 1, stdout);
putchar ('\n');
}
/* Received from server, thus more responses are expected. */
}
else
return gpg_error (GPG_ERR_ASS_INV_RESPONSE);
}
}
}
/* Connect to the agent and send the standard options. */
static assuan_context_t
start_agent (void)
{
gpg_error_t err;
assuan_context_t ctx;
session_env_t session_env;
session_env = session_env_new ();
if (!session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
if (opt.use_dirmngr)
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program,
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
else if (opt.use_keyboxd)
err = start_new_keyboxd (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.keyboxd_program,
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
else
err = start_new_gpg_agent (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
NULL, NULL,
session_env,
opt.autostart,
!opt.quiet, 0,
NULL, NULL);
session_env_release (session_env);
if (err)
{
if (!opt.autostart
&& (gpg_err_code (err)
== (opt.use_dirmngr? GPG_ERR_NO_DIRMNGR :
opt.use_keyboxd? GPG_ERR_NO_KEYBOXD : GPG_ERR_NO_AGENT)))
{
/* In the no-autostart case we don't make gpg-connect-agent
fail on a missing server. */
log_info (opt.use_dirmngr?
_("no dirmngr running in this session\n"):
opt.use_keyboxd?
_("no keybox daemon running in this session\n"):
_("no gpg-agent running in this session\n"));
exit (0);
}
else
{
log_error (_("error sending standard options: %s\n"),
gpg_strerror (err));
exit (1);
}
}
return ctx;
}
diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c
index 4a18b97bd..61877b171 100644
--- a/tools/gpg-pair-tool.c
+++ b/tools/gpg-pair-tool.c
@@ -1,1964 +1,1964 @@
/* gpg-pair-tool.c - The tool to run the pairing protocol.
* Copyright (C) 2018 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Protocol:
*
* Initiator Responder
* | |
* | COMMIT |
* |-------------------->|
* | |
* | DHPART1 |
* |<--------------------|
* | |
* | DHPART2 |
* |-------------------->|
* | |
* | CONFIRM |
* |<--------------------|
* | |
*
* The initiator creates a keypar (PKi,SKi) and sends this COMMIT
* message to the responder:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 1 (COMMIT)
* 8 byte SessionId, value: 8 random bytes
* 1 byte Realm, value 1
* 2 byte reserved, value 0
* 5 byte ExpireTime, value: seconds since Epoch as an unsigned int.
* 32 byte Hash(PKi)
*
* The initiator also needs to locally store the sessionid, the realm,
* the expiration time, the keypair and a hash of the entire message
* sent.
*
* The responder checks that the received message has not expired and
* stores sessionid, realm, expiretime and the Hash(PKi). The
* Responder then creates and locally stores its own keypair (PKr,SKr)
* and sends the DHPART1 message back:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 2 (DHPART1)
* 8 byte SessionId from COMMIT message
* 32 byte PKr
* 32 byte Hash(Hash(COMMIT) || DHPART1[0..47])
*
* Note that Hash(COMMIT) is the hash over the entire received COMMIT
* message. DHPART1[0..47] are the first 48 bytes of the created
* DHPART1 message.
*
* The Initiator receives the DHPART1 message and checks that the hash
* matches. Although this hash is easily malleable it is later in the
* protocol used to assert the integrity of all messages. The
* Initiator then computes the shared master secret from its SKi and
* the received PKr. Using this master secret several keys are
* derived:
*
* - HMACi-key using the label "GPG-pa1-HMACi-key".
* - SYMx-key using the label "GPG-pa1-SYMx-key"
*
* For details on the KDF see the implementation of the function kdf.
* The master secret is stored securily in the local state. The
* DHPART2 message is then created and send to the Responder:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 3 (DHPART2)
* 8 byte SessionId from COMMIT message
* 32 byte PKi
* 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key)
*
* The Responder receives the DHPART2 message and checks that the hash
* of the received PKi matches the Hash(PKi) value as received earlier
* with the COMMIT message. The Responder now also computes the
- * shared master secret from its SKr and the recived PKi and derives
+ * shared master secret from its SKr and the received PKi and derives
* the keys:
*
* - HMACi-key using the label "GPG-pa1-HMACi-key".
* - HMACr-key using the label "GPG-pa1-HMACr-key".
* - SYMx-key using the label "GPG-pa1-SYMx-key"
* - SAS using the label "GPG-pa1-SAS"
*
* With these keys the MAC from the received DHPART2 message is
* checked. On success a SAS is displayed to the user and a CONFIRM
* message send back:
*
* 7 byte Magic, value: "GPG-pa1"
* 1 byte MessageType, value 4 (CONFIRM)
* 8 byte SessionId from COMMIT message
* 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key)
*
* The Initiator receives this CONFIRM message, gets the master shared
* secrey from its local state and derives the keys. It checks the
* the MAC in the received CONFIRM message and ask the user to enter
* the SAS as displayed by the responder. Iff the SAS matches the
* master key is flagged as confirmed and the Initiator may now use a
* derived key to send encrypted data to the Responder.
*
* In case the Responder also needs to send encrypted data we need to
* introduce another final message to tell the responder that the
* Initiator validated the SAS.
*
* TODO: Encrypt the state files using a key stored in gpg-agent's cache.
*
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdarg.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/name-value.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oOutput = 'o',
oArmor = 'a',
aInitiate = 400,
aRespond = 401,
aGet = 402,
aCleanup = 403,
oDebug = 500,
oStatusFD,
oHomedir,
oSAS,
oDummy
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, ("@Commands:\n ")),
ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")),
ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")),
ARGPARSE_c (aGet, "get", N_("return the keys")),
ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")),
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_end ()
};
/* We keep all global options in the structure OPT. */
static struct
{
int verbose;
unsigned int debug;
int quiet;
int armor;
const char *output;
estream_t statusfp;
unsigned int ttl;
const char *sas;
} opt;
/* Debug values and macros. */
#define DBG_MESSAGE_VALUE 2 /* Debug the messages. */
#define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */
#define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */
#define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MESSAGE_VALUE, "message" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ 0, NULL }
};
/* The directory name below the cache dir to store paring states. */
#define PAIRING_STATE_DIR "state"
/* Message types. */
#define MSG_TYPE_COMMIT 1
#define MSG_TYPE_DHPART1 2
#define MSG_TYPE_DHPART2 3
#define MSG_TYPE_CONFIRM 4
/* Realm values. */
#define REALM_STANDARD 1
/* Local prototypes. */
static void wrong_args (const char *text) GPGRT_ATTR_NORETURN;
static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format,
...) GPGRT_ATTR_PRINTF(3,4);
static void *hash_data (void *result, size_t resultsize,
...) GPGRT_ATTR_SENTINEL(0);
static void *hmac_data (void *result, size_t resultsize,
const unsigned char *key, size_t keylen,
...) GPGRT_ATTR_SENTINEL(0);
static gpg_error_t command_initiate (void);
static gpg_error_t command_respond (void);
static gpg_error_t command_cleanup (void);
static gpg_error_t command_get (const char *sessionidstr);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "LGPL-2.1-or-later"; break;
case 11: p = "gpg-pair-tool"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-pair-tool [command] [options] [args] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-pair-tool [command] [options] [args]\n"
"Client to run the pairing protocol\n");
break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), strusage (11), text);
exit (2);
}
/* Set the status FD. */
static void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr)
es_fclose (opt.statusfp);
opt.statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
opt.statusfp = es_stdout;
else if (fd == 2)
opt.statusfp = es_stderr;
else
opt.statusfp = es_fdopen (fd, "w");
if (!opt.statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
-/* Write a status line with code NO followed by the outout of the
+/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
static void
write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!opt.statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", opt.statusfp);
es_fputs (get_status_string (no), opt.statusfp);
if (format)
{
es_putc (' ', opt.statusfp);
va_start (arg_ptr, format);
es_vfprintf (opt.statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', opt.statusfp);
}
/* gpg-pair-tool main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
gpgrt_argparse_t pargs = { &argc, &argv };
enum cmd_and_opt_values cmd = 0;
opt.ttl = 8*3600; /* Default to 8 hours. */
gnupg_reopen_std ("gpg-pair-tool");
gpgrt_set_strusage (my_strusage);
log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Parse the command line. */
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oArmor: opt.armor = 1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oOutput:
opt.output = pargs.r.ret_str;
break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
case oSAS:
opt.sas = pargs.r.ret_str;
break;
case aInitiate:
case aRespond:
case aGet:
case aCleanup:
if (cmd && cmd != pargs.r_opt)
log_error (_("conflicting commands\n"));
else
cmd = pargs.r_opt;
break;
default: pargs.err = ARGPARSE_PRINT_WARNING; break;
}
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
}
gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */
if (opt.sas)
{
if (strlen (opt.sas) != 11
|| !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2)
|| opt.sas[3] != '-'
|| !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6)
|| opt.sas[7] != '-'
|| !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10))
log_error ("invalid formatted SAS\n");
}
/* Stop if any error, inclduing ARGPARSE_PRINT_WARNING, occurred. */
if (log_get_errorcount (0))
exit (2);
if (DBG_CRYPTO)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2);
/* Now run the requested command. */
switch (cmd)
{
case aInitiate:
if (argc)
wrong_args ("--initiate");
err = command_initiate ();
break;
case aRespond:
if (argc)
wrong_args ("--respond");
err = command_respond ();
break;
case aGet:
if (argc > 1)
wrong_args ("--respond [sessionid]");
err = command_get (argc? *argv:NULL);
break;
case aCleanup:
if (argc)
wrong_args ("--cleanup");
err = command_cleanup ();
break;
default:
gpgrt_usage (1);
err = 0;
break;
}
if (err)
write_status (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
write_status (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Wrapper around nvc_new which terminates in the error case. */
static nvc_t
xnvc_new (void)
{
nvc_t c = nvc_new ();
if (!c)
log_fatal ("error creating NVC object: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return c;
}
/* Wrapper around nvc_set which terminates in the error case. */
static void
xnvc_set (nvc_t nvc, const char *name, const char *value)
{
gpg_error_t err = nvc_set (nvc, name, value);
if (err)
log_fatal ("error updating NVC object: %s\n", gpg_strerror (err));
}
/* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as
* value. Terminates in the error case. */
static void
xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen)
{
char *hex;
hex = bin2hex (buffer, buflen, NULL);
if (!hex)
xoutofcore ();
strlwr (hex);
xnvc_set (nvc, name, hex);
xfree (hex);
}
/* Call nvc_set with a value created from the string generated using
* the printf style FORMAT. Terminates in the error case. */
static void
xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...)
{
va_list arg_ptr;
char *buffer;
va_start (arg_ptr, format);
if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0)
log_fatal ("estream_asprintf failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
va_end (arg_ptr);
xnvc_set (nvc, name, buffer);
xfree (buffer);
}
/* Return the string for the first entry in NVC with NAME. If NAME is
* missing, an empty string is returned. The returned string is a
* pointer into NVC. */
static const char *
xnvc_get_string (nvc_t nvc, const char *name)
{
nve_t item;
if (!nvc)
return "";
item = nvc_lookup (nvc, name);
if (!item)
return "";
return nve_value (item);
}
/* Return a string for MSGTYPE. */
const char *
msgtypestr (int msgtype)
{
switch (msgtype)
{
case MSG_TYPE_COMMIT: return "Commit";
case MSG_TYPE_DHPART1: return "DHPart1";
case MSG_TYPE_DHPART2: return "DHPart2";
case MSG_TYPE_CONFIRM: return "Confirm";
}
return "?";
}
/* Private to {get,set}_session_id(). */
static struct {
int initialized;
unsigned char sessid[8];
} session_id;
/* Return the 8 octet session. */
static unsigned char *
get_session_id (void)
{
if (!session_id.initialized)
{
session_id.initialized = 1;
gcry_create_nonce (session_id.sessid, sizeof session_id.sessid);
}
return session_id.sessid;
}
static void
set_session_id (const void *sessid, size_t len)
{
log_assert (!session_id.initialized);
if (len > sizeof session_id.sessid)
len = sizeof session_id.sessid;
memcpy (session_id.sessid, sessid, len);
if (len < sizeof session_id.sessid)
memset (session_id.sessid+len, 0, sizeof session_id.sessid - len);
session_id.initialized = 1;
}
/* Return a string with the hexified session id. */
static const char *
get_session_id_hex (void)
{
static char hexstr[16+1];
bin2hex (get_session_id (), 8, hexstr);
strlwr (hexstr);
return hexstr;
}
/* Return a fixed string with the directory used to store the state of
* pairings. On error a diagnostic is printed but the file name is
* returned anyway. It is expected that the expected failure of the
* following open is responsible for error handling. */
static const char *
get_pairing_statedir (void)
{
static char *fname;
gpg_error_t err = 0;
char *tmpstr;
struct stat statbuf;
if (fname)
return fname;
fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL);
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
fname, gpg_strerror (err) );
}
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL);
xfree (fname);
fname = tmpstr;
if (stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
{
if (!err)
{
err = gpg_error_from_syserror ();
log_error (_("can't create directory '%s': %s\n"),
fname, gpg_strerror (err) );
}
}
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
return fname;
}
/* Open the pairing state file. SESSIONID is a 8 byte buffer with the
* session-id. If CREATE_FLAG is set the file is created and will
* always return a valid stream. If CREATE_FLAG is not set the file
* is opened for reading and writing. If the file does not exist NULL
* is return; in all other error cases the process is terminated. If
* R_FNAME is not NULL the name of the file is stored there and the
* caller needs to free it. */
static estream_t
open_pairing_state (const unsigned char *sessionid, int create_flag,
char **r_fname)
{
gpg_error_t err;
char *fname, *tmpstr;
estream_t fp;
/* The filename is the session id with a "pa1" suffix. Note that
* the state dir may eventually be used for other purposes as well
* and thus the suffix identifies that the file belongs to this
* tool. We use lowercase file names for no real reason. */
tmpstr = bin2hex (sessionid, 8, NULL);
if (!tmpstr)
xoutofcore ();
strlwr (tmpstr);
fname = xstrconcat (tmpstr, ".pa1", NULL);
xfree (tmpstr);
tmpstr = make_filename (get_pairing_statedir (), fname, NULL);
xfree (fname);
fname = tmpstr;
fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw");
if (!fp)
{
err = gpg_error_from_syserror ();
if (create_flag)
{
/* We should always be able to create a file. Also we use a
* 64 bit session id, it is theoretically possible that such
* a session already exists. However, that is rare enough
* and thus the fatal error message should still be okay. */
log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err));
}
else if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* That is an expected error; return NULL. */
}
else
{
log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err));
}
}
if (r_fname)
*r_fname = fname;
else
xfree (fname);
return fp;
}
/* Write the state to a possible new state file. */
static void
write_state (nvc_t state, int create_flag)
{
gpg_error_t err;
char *fname = NULL;
estream_t fp;
fp = open_pairing_state (get_session_id (), create_flag, &fname);
log_assert (fp);
err = nvc_write (state, fp);
if (err)
{
es_fclose (fp);
gnupg_remove (fname);
log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
}
/* If we did not create the file, we need to truncate the file. */
if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp)))
{
err = gpg_error_from_syserror ();
log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err));
}
if (es_ferror (fp) || es_fclose (fp))
{
err = gpg_error_from_syserror ();
es_fclose (fp);
gnupg_remove (fname);
log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err));
}
}
/* Read the state into a newly allocated state object and store that
* at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned
* and as with all errors NULL is tored at R_STATE. SESSIONID is an
* input with the 8 session id. */
static gpg_error_t
read_state (nvc_t *r_state)
{
gpg_error_t err;
char *fname = NULL;
estream_t fp;
nvc_t state = NULL;
nve_t item;
const char *value;
unsigned long expire;
*r_state = NULL;
fp = open_pairing_state (get_session_id (), 0, &fname);
if (!fp)
return gpg_error (GPG_ERR_NOT_FOUND);
err = nvc_parse (&state, NULL, fp);
if (err)
{
log_info ("failed to parse state file '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
/* Check whether the state already expired. */
item = nvc_lookup (state, "Expires:");
if (!item)
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' not found");
goto leave;
}
value = nve_value (item);
if (!value || !(expire = strtoul (value, NULL, 10)))
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' has an invalid value");
goto leave;
}
if (expire <= gnupg_get_time ())
{
es_fclose (fp);
fp = NULL;
if (gnupg_remove (fname))
{
err = gpg_error_from_syserror ();
log_info ("failed to delete state file '%s': %s\n",
fname, gpg_strerror (err));
}
else if (opt.verbose)
log_info ("state file '%s' deleted\n", fname);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
*r_state = state;
state = NULL;
leave:
nvc_release (state);
es_fclose (fp);
return err;
}
/* Send (MSG,MSGLEN) to the output device. */
static void
send_message (const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
if (opt.verbose)
log_info ("session %s: sending %s message\n",
get_session_id_hex (), msgtypestr (msg[7]));
if (DBG_MESSAGE)
log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7]));
/* FIXME: For now only stdout. */
if (opt.armor)
{
gpgrt_b64state_t state;
state = gpgrt_b64enc_start (es_stdout, "");
if (!state)
log_fatal ("error setting up base64 encoder: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
err = gpgrt_b64enc_write (state, msg, msglen);
if (!err)
err = gpgrt_b64enc_finish (state);
if (err)
log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err));
}
else
{
if (es_fwrite (msg, msglen, 1, es_stdout) != 1)
log_fatal ("error writing to stdout: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
}
es_fputc ('\n', es_stdout);
}
/* Read a message from stdin and store it at the address (R_MSG,
* R_MSGLEN). This function detects armoring and removes it. On
* error NULL is stored at R_MSG, a diagnostic printed and an error
* code returned. The returned message has a proper message type and
* an appropriate length. The message type is stored at R_MSGTYPE and
- * if a state is availabale it is stored at R_STATE. */
+ * if a state is available it is stored at R_STATE. */
static gpg_error_t
read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype,
nvc_t *r_state)
{
gpg_error_t err;
unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */
size_t msglen;
size_t reqlen;
*r_msg = NULL;
*r_state = NULL;
es_setvbuf (es_stdin, NULL, _IONBF, 0);
es_set_binary (es_stdin);
if (es_read (es_stdin, msg, sizeof msg, &msglen))
{
err = gpg_error_from_syserror ();
log_error ("error reading from message: %s\n", gpg_strerror (err));
return err;
}
if (msglen > 4 && !memcmp (msg, "R1BH", 4))
{
/* This is base64 of the first 3 bytes. */
gpgrt_b64state_t state = gpgrt_b64dec_start (NULL);
if (!state)
log_fatal ("error setting up base64 decoder: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
err = gpgrt_b64dec_proc (state, msg, msglen, &msglen);
gpgrt_b64dec_finish (state);
if (err)
{
log_error ("error decoding message: %s\n", gpg_strerror (err));
return err;
}
}
if (msglen < 16 || memcmp (msg, "GPG-pa1", 7))
{
log_error ("error parsing message: %s\n",
msglen? "invalid header":"empty message");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
switch (msg[7])
{
case MSG_TYPE_COMMIT: reqlen = 56; break;
case MSG_TYPE_DHPART1: reqlen = 80; break;
case MSG_TYPE_DHPART2: reqlen = 80; break;
case MSG_TYPE_CONFIRM: reqlen = 48; break;
default:
log_error ("error parsing message: %s\n", "invalid message type");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
if (msglen < reqlen)
{
log_error ("error parsing message: %s\n", "message too short");
return gpg_error (GPG_ERR_INV_RESPONSE);
}
if (DBG_MESSAGE)
log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7]));
/* Note that we ignore any garbage at the end of a message. */
msglen = reqlen;
set_session_id (msg+8, 8);
if (opt.verbose)
log_info ("session %s: received %s message\n",
get_session_id_hex (), msgtypestr (msg[7]));
/* Read the state. */
err = read_state (r_state);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
return err;
*r_msg = xmalloc (msglen);
memcpy (*r_msg, msg, msglen);
*r_msglen = msglen;
*r_msgtype = msg[7];
return err;
}
/* Display the Short Authentication String (SAS). If WAIT is true the
* function waits until the user has entered the SAS as seen at the
* peer.
*
* To construct the SAS we take the 4 most significant octets of HASH,
* interpret them as a 32 bit big endian unsigned integer, divide that
* integer by 10^9 and take the remainder. The remainder is displayed
* as 3 groups of 3 decimal digits delimited by a hyphens. This gives
* a search space of close to 2^30 and is still easy to compare.
*/
static gpg_error_t
display_sas (const unsigned char *hash, size_t hashlen, int wait)
{
gpg_error_t err = 0;
unsigned long sas = 0;
char sasbuf[12];
log_assert (hashlen >= 4);
sas |= (unsigned long)hash[20] << 24;
sas |= (unsigned long)hash[21] << 16;
sas |= (unsigned long)hash[22] << 8;
sas |= (unsigned long)hash[23];
sas %= 1000000000ul;
snprintf (sasbuf, sizeof sasbuf, "%09lu", sas);
memmove (sasbuf+8, sasbuf+6, 3);
memmove (sasbuf+4, sasbuf+3, 3);
sasbuf[3] = sasbuf[7] = '-';
sasbuf[11] = 0;
if (wait)
log_info ("Please check the SAS:\n");
else
log_info ("Please note the SAS:\n");
log_info ("\n");
log_info (" %s\n", sasbuf);
log_info ("\n");
if (wait)
{
if (!opt.sas || strcmp (sasbuf, opt.sas))
err = gpg_error (GPG_ERR_NOT_CONFIRMED);
else
log_info ("SAS confirmed\n");
}
if (err)
log_info ("checking SAS failed: %s\n", gpg_strerror (err));
return err;
}
static gpg_error_t
create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len,
unsigned char *dh_public, size_t dh_public_len)
{
gpg_error_t err;
unsigned char *p;
/* We need a temporary buffer for the public key. Check the length
* for the later memcpy. */
if (dh_public_len < 32 || dh_secret_len < 32)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
if (gcry_ecc_get_algo_keylen (GCRY_ECC_CURVE25519) > dh_public_len)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
p = gcry_random_bytes (32, GCRY_VERY_STRONG_RANDOM);
if (!p)
return gpg_error_from_syserror ();
memcpy (dh_secret, p, 32);
xfree (p);
err = gcry_ecc_mul_point (GCRY_ECC_CURVE25519, dh_public, dh_secret, NULL);
if (err)
return err;
if (DBG_CRYPTO)
{
log_printhex (dh_secret, 32, "DH secret:");
log_printhex (dh_public, 32, "DH public:");
}
return 0;
}
/* SHA256 the data given as varargs tuples of (const void*, size_t)
* and store the result in RESULT. The end of the list is indicated
* by a NULL element in a tuple. RESULTLEN gives the length of the
* RESULT buffer which must be at least 32. Note that the second item
* of the tuple is the length and it is a size_t. */
static void *
hash_data (void *result, size_t resultsize, ...)
{
va_list arg_ptr;
gpg_error_t err;
gcry_md_hd_t hd;
const void *data;
size_t datalen;
log_assert (resultsize >= 32);
err = gcry_md_open (&hd, GCRY_MD_SHA256, 0);
if (err)
log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err));
/* log_printhex ("", 0, "Hash-256:"); */
va_start (arg_ptr, resultsize);
while ((data = va_arg (arg_ptr, const void *)))
{
datalen = va_arg (arg_ptr, size_t);
/* log_printhex (data, datalen, " data:"); */
gcry_md_write (hd, data, datalen);
}
va_end (arg_ptr);
memcpy (result, gcry_md_read (hd, 0), 32);
/* log_printhex (result, 32, " result:"); */
gcry_md_close (hd);
return result;
}
/* HMAC-SHA256 the data given as varargs tuples of (const void*,
* size_t) using (KEYLEN,KEY) and store the result in RESULT. The end
* of the list is indicated by a NULL element in a tuple. RESULTLEN
* gives the length of the RESULT buffer which must be at least 32.
* Note that the second item of the tuple is the length and it is a
* size_t. */
static void *
hmac_data (void *result, size_t resultsize,
const unsigned char *key, size_t keylen, ...)
{
va_list arg_ptr;
gpg_error_t err;
gcry_mac_hd_t hd;
const void *data;
size_t datalen;
log_assert (resultsize >= 32);
err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL);
if (err)
log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err));
err = gcry_mac_setkey (hd, key, keylen);
if (err)
log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err));
/* log_printhex (key, keylen, "HMAC-key:"); */
va_start (arg_ptr, keylen);
while ((data = va_arg (arg_ptr, const void *)))
{
datalen = va_arg (arg_ptr, size_t);
/* log_printhex (data, datalen, " data:"); */
err = gcry_mac_write (hd, data, datalen);
if (err)
log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err));
}
va_end (arg_ptr);
err = gcry_mac_read (hd, result, &resultsize);
if (err || resultsize != 32)
log_fatal ("error reading MAC value: %s\n", gpg_strerror (err));
/* log_printhex (result, resultsize, " result:"); */
gcry_mac_close (hd);
return result;
}
/* Key derivation function:
*
* FIXME(doc)
*/
static void
kdf (unsigned char *result, size_t resultlen,
const unsigned char *master, size_t masterlen,
const unsigned char *sessionid, size_t sessionidlen,
const unsigned char *expire, size_t expirelen,
const char *label)
{
log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5);
log_assert (*label);
log_assert (resultlen == 32);
hmac_data (result, resultlen, master, masterlen,
"\x00\x00\x00\x01", (size_t)4, /* Counter=1*/
label, strlen (label) + 1, /* Label, 0x00 */
sessionid, sessionidlen, /* Context */
expire, expirelen, /* Context */
"\x00\x00\x01\x00", (size_t)4, /* L=256 */
NULL);
}
static gpg_error_t
compute_master_secret (unsigned char *master, size_t masterlen,
const unsigned char *sk_a, size_t sk_a_len,
const unsigned char *pk_b, size_t pk_b_len)
{
gpg_error_t err;
log_assert (masterlen == 32);
log_assert (sk_a_len == 32);
log_assert (pk_b_len == 32);
err = gcry_ecc_mul_point (GCRY_ECC_CURVE25519, master, sk_a, pk_b);
if (err)
log_error ("error computing DH: %s\n", gpg_strerror (err));
return err;
}
/* We are the Initiator: Create the commit message. This function
* sends the COMMIT message and writes STATE. */
static gpg_error_t
make_msg_commit (nvc_t state)
{
gpg_error_t err;
uint64_t now, expire;
unsigned char secret[32];
unsigned char public[32];
unsigned char *newmsg;
size_t newmsglen;
unsigned char tmphash[32];
err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
if (err)
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
now = gnupg_get_time ();
expire = now + opt.ttl;
newmsglen = 7+1+8+1+2+5+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_COMMIT;
memcpy (newmsg+8, get_session_id (), 8);
newmsg[16] = REALM_STANDARD;
newmsg[17] = 0;
newmsg[18] = 0;
newmsg[19] = expire >> 32;
newmsg[20] = expire >> 24;
newmsg[21] = expire >> 16;
newmsg[22] = expire >> 8;
newmsg[23] = expire;
gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32);
/* Create the state file. */
xnvc_set (state, "State:", "Commit-sent");
xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
xnvc_set_hex (state, "DH-PKi:", public, 32);
xnvc_set_hex (state, "DH-SKi:", secret, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-Commit:", tmphash, 32);
/* Write the state. Note that we need to create it. The state
* updating should in theory be done atomically with send_message.
* However, we can't assure that the message will actually be
* delivered and thus it doesn't matter whether we have an already
* update state when we later fail in send_message. */
write_state (state, 1);
/* Write the message. */
send_message (newmsg, newmsglen);
xfree (newmsg);
return err;
}
/* We are the Responder: Process a commit message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the DHPart1 message and writes STATE. */
static gpg_error_t
proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
uint64_t now, expire;
unsigned char tmphash[32];
unsigned char secret[32];
unsigned char public[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 56);
now = gnupg_get_time ();
/* Check that the message has not expired. */
expire = (uint64_t)msg[19] << 32;
expire |= (uint64_t)msg[20] << 24;
expire |= (uint64_t)msg[21] << 16;
expire |= (uint64_t)msg[22] << 8;
expire |= (uint64_t)msg[23];
if (expire < now)
{
log_error ("received %s message is too old\n",
msgtypestr (MSG_TYPE_COMMIT));
err = gpg_error (GPG_ERR_TOO_OLD);
goto leave;
}
/* Create the response. */
err = create_dh_keypair (secret, sizeof secret, public, sizeof public );
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
newmsglen = 7+1+8+32+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_DHPART1;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
memcpy (newmsg+16, public, 32); /* PKr */
/* Hash(Hash(Commit) || DHPart1[0..47]) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hash_data (newmsg+48, 32,
tmphash, sizeof tmphash,
newmsg, (size_t)48,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "DHPart1-sent");
xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now);
xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire);
xnvc_set_hex (state, "Hash-PKi:", msg+24, 32);
xnvc_set_hex (state, "DH-PKr:", public, 32);
xnvc_set_hex (state, "DH-SKr:", secret, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32);
/* Write the state. Note that we need to create it. */
write_state (state, 1);
/* Write the message. */
send_message (newmsg, newmsglen);
leave:
xfree (newmsg);
return err;
}
/* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the DHPart2 message and writes STATE. */
static gpg_error_t
proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
unsigned char pki[32];
unsigned char pkr[32];
unsigned char ski[32];
unsigned char master[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char hmacikey[32];
unsigned char symxkey[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 80);
/* Check that the message includes the Hash(Commit). */
if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-Commit' in our state file\n");
goto leave;
}
hash_data (tmphash, 32,
hash, sizeof hash,
msg, (size_t)48,
NULL);
if (memcmp (msg+48, tmphash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_DHPART1), "Bad Hash");
goto leave;
}
/* Check that the received PKr is different from our PKi and copy
* PKr into PKR. */
if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-PKi' in our state file\n");
goto leave;
}
if (!memcmp (msg+16, pki, 32))
{
/* This can only happen if the state file leaked to the
* responder. */
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("received our own public key PKi instead of PKr\n");
goto leave;
}
memcpy (pkr, msg+16, 32);
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get our secret from the state. */
if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-SKi' in our state file\n");
goto leave;
}
/* Compute the shared secrets. */
err = compute_master_secret (master, sizeof master,
ski, sizeof ski, pkr, sizeof pkr);
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
kdf (hmacikey, sizeof hmacikey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACi-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
/* Create the response. */
newmsglen = 7+1+8+32+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_DHPART2;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
memcpy (newmsg+16, pki, 32); /* PKi */
/* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey,
tmphash, sizeof tmphash,
newmsg, (size_t)48,
symxkey, sizeof symxkey,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "DHPart2-sent");
xnvc_set_hex (state, "DH-Master:", master, sizeof master);
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen);
xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32);
/* Write the state. */
write_state (state, 0);
/* Write the message. */
send_message (newmsg, newmsglen);
leave:
xfree (newmsg);
return err;
}
/* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Sends the CONFIRM message and writes STATE. */
static gpg_error_t
proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char pki[32];
unsigned char pkr[32];
unsigned char skr[32];
unsigned char master[32];
unsigned char hmacikey[32];
unsigned char hmacrkey[32];
unsigned char symxkey[32];
unsigned char sas[32];
unsigned char *newmsg = NULL;
size_t newmsglen;
log_assert (msglen >= 80);
/* Check that the PKi in the message matches the Hash(Pki) received
* with the Commit message. */
memcpy (pki, msg + 16, 32);
gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32);
if (hex2bin (xnvc_get_string (state, "Hash-PKi:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-PKi' in our state file\n");
goto leave;
}
if (memcmp (hash, tmphash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("Initiator sent a different key in %s than announced in %s\n",
msgtypestr (MSG_TYPE_DHPART2),
msgtypestr (MSG_TYPE_COMMIT));
goto leave;
}
/* Check that the received PKi is different from our PKr. */
if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-PKr' in our state file\n");
goto leave;
}
if (!memcmp (pkr, pki, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("Initiator sent our own PKr back\n");
goto leave;
}
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get our secret from the state. */
if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-SKr' in our state file\n");
goto leave;
}
/* Compute the shared secrets. */
err = compute_master_secret (master, sizeof master,
skr, sizeof skr, pki, sizeof pki);
if (err)
{
log_error ("creating DH keypair failed: %s\n", gpg_strerror (err));
goto leave;
}
kdf (hmacikey, sizeof hmacikey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACi-key");
kdf (hmacrkey, sizeof hmacrkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACr-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
kdf (sas, sizeof sas,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SAS");
/* Check the MAC from the message which is
* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key).
* For that we need to fetch the stored hash from the state. */
if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-DHPart1' in our state file\n");
goto leave;
}
hmac_data (hash, 32, hmacikey, sizeof hmacikey,
tmphash, sizeof tmphash,
msg, 48,
symxkey, sizeof symxkey,
NULL);
if (memcmp (msg+48, hash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_DHPART2), "Bad MAC");
goto leave;
}
/* Create the response. */
newmsglen = 7+1+8+32;
newmsg = xmalloc (newmsglen);
memcpy (newmsg+0, "GPG-pa1", 7);
newmsg[7] = MSG_TYPE_CONFIRM;
memcpy (newmsg+8, msg + 8, 8); /* SessionID. */
/* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */
gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen);
hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey,
tmphash, sizeof tmphash,
newmsg, (size_t)16,
symxkey, sizeof symxkey,
NULL);
/* Update the state. */
xnvc_set (state, "State:", "Confirm-sent");
xnvc_set_hex (state, "DH-Master:", master, sizeof master);
/* Write the state. */
write_state (state, 0);
/* Write the message. */
send_message (newmsg, newmsglen);
display_sas (sas, sizeof sas, 0);
leave:
xfree (newmsg);
return err;
}
/* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN)
* which has already been validated to have a correct header and
* message type. Does not send anything back. */
static gpg_error_t
proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen)
{
gpg_error_t err;
unsigned char hash[32];
unsigned char tmphash[32];
unsigned char master[32];
uint64_t expire;
unsigned char expirebuf[5];
unsigned char hmacrkey[32];
unsigned char symxkey[32];
unsigned char sas[32];
log_assert (msglen >= 48);
/* Put the expire value into a buffer. */
expire = string_to_u64 (xnvc_get_string (state, "Expires:"));
if (!expire)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no 'Expire' in our state file\n");
goto leave;
}
expirebuf[0] = expire >> 32;
expirebuf[1] = expire >> 24;
expirebuf[2] = expire >> 16;
expirebuf[3] = expire >> 8;
expirebuf[4] = expire;
/* Get the master secret. */
if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'DH-Master' in our state file\n");
goto leave;
}
kdf (hmacrkey, sizeof hmacrkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-HMACr-key");
kdf (symxkey, sizeof symxkey,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SYMx-key");
kdf (sas, sizeof sas,
master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf,
"GPG-pa1-SAS");
/* Check the MAC from the message which is */
/* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */
if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"),
tmphash, sizeof tmphash) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("no or garbled 'Hash-DHPart2' in our state file\n");
goto leave;
}
hmac_data (hash, 32, hmacrkey, sizeof hmacrkey,
tmphash, sizeof tmphash,
msg, (size_t)16,
symxkey, sizeof symxkey,
NULL);
if (!memcmp (msg+48, hash, 32))
{
err = gpg_error (GPG_ERR_BAD_DATA);
log_error ("manipulation of received %s message detected: %s\n",
msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC");
goto leave;
}
err = display_sas (sas, sizeof sas, 1);
if (err)
goto leave;
/* Update the state. */
xnvc_set (state, "State:", "Confirmed");
/* Write the state. */
write_state (state, 0);
leave:
return err;
}
/* Expire old state files. This loops over all state files and remove
* those which are expired. */
static void
expire_old_states (void)
{
gpg_error_t err = 0;
const char *dirname;
DIR *dir = NULL;
struct dirent *dir_entry;
char *fname = NULL;
estream_t fp = NULL;
nvc_t nvc = NULL;
nve_t item;
const char *value;
unsigned long expire;
unsigned long now = gnupg_get_time ();
dirname = get_pairing_statedir ();
dir = opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
while ((dir_entry = readdir (dir)))
{
if (strlen (dir_entry->d_name) != 16+4
|| strcmp (dir_entry->d_name + 16, ".pa1"))
continue;
xfree (fname);
fname = make_filename (dirname, dir_entry->d_name, NULL);
es_fclose (fp);
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_info ("failed to open state file '%s': %s\n",
fname, gpg_strerror (err));
continue;
}
nvc_release (nvc);
/* NB.: The following is similar to code in read_state. */
err = nvc_parse (&nvc, NULL, fp);
if (err)
{
log_info ("failed to parse state file '%s': %s\n",
fname, gpg_strerror (err));
continue; /* Skip */
}
item = nvc_lookup (nvc, "Expires:");
if (!item)
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' not found");
continue; /* Skip */
}
value = nve_value (item);
if (!value || !(expire = strtoul (value, NULL, 10)))
{
log_info ("invalid state file '%s': %s\n",
fname, "field 'expire' has an invalid value");
continue; /* Skip */
}
if (expire <= now)
{
es_fclose (fp);
fp = NULL;
if (gnupg_remove (fname))
{
err = gpg_error_from_syserror ();
log_info ("failed to delete state file '%s': %s\n",
fname, gpg_strerror (err));
}
else if (opt.verbose)
log_info ("state file '%s' deleted\n", fname);
}
}
leave:
if (err)
log_error ("expiring old states in '%s' failed: %s\n",
dirname, gpg_strerror (err));
if (dir)
closedir (dir);
es_fclose (fp);
xfree (fname);
}
/* Initiate a pairing. The output needs to be conveyed to the
* peer */
static gpg_error_t
command_initiate (void)
{
gpg_error_t err;
nvc_t state;
state = xnvc_new ();
xnvc_set (state, "Version:", "GPG-pa1");
xnvc_set_hex (state, "Session:", get_session_id (), 8);
xnvc_set (state, "Role:", "Initiator");
err = make_msg_commit (state);
nvc_release (state);
return err;
}
/* Helper for command_respond(). */
static gpg_error_t
expect_state (int msgtype, const char *statestr, const char *expected)
{
if (strcmp (statestr, expected))
{
log_error ("received %s message in %s state (should be %s)\n",
msgtypestr (msgtype), statestr, expected);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
return 0;
}
/* Respond to a pairing intiation. This is used by the peer and later
* by the original responder. Depending on the state the output needs
* to be conveyed to the peer. */
static gpg_error_t
command_respond (void)
{
gpg_error_t err;
unsigned char *msg;
size_t msglen = 0; /* In case that read_message returns an error. */
int msgtype = 0; /* ditto. */
nvc_t state;
const char *rolestr;
const char *statestr;
err = read_message (&msg, &msglen, &msgtype, &state);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
rolestr = xnvc_get_string (state, "Role:");
statestr = xnvc_get_string (state, "State:");
if (DBG_MESSAGE)
{
if (!state)
log_debug ("no state available\n");
else
log_debug ("we are %s, our current state is %s\n", rolestr, statestr);
log_debug ("got message of type %s (%d)\n",
msgtypestr (msgtype), msgtype);
}
if (!state)
{
if (msgtype == MSG_TYPE_COMMIT)
{
state = xnvc_new ();
xnvc_set (state, "Version:", "GPG-pa1");
xnvc_set_hex (state, "Session:", get_session_id (), 8);
xnvc_set (state, "Role:", "Responder");
err = proc_msg_commit (state, msg, msglen);
}
else
{
log_error ("%s message expected but got %s\n",
msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype));
if (msgtype == MSG_TYPE_DHPART1)
log_info ("the pairing probably took too long and timed out\n");
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else if (!strcmp (rolestr, "Initiator"))
{
if (msgtype == MSG_TYPE_DHPART1)
{
if (!(err = expect_state (msgtype, statestr, "Commit-sent")))
err = proc_msg_dhpart1 (state, msg, msglen);
}
else if (msgtype == MSG_TYPE_CONFIRM)
{
if (!(err = expect_state (msgtype, statestr, "DHPart2-sent")))
err = proc_msg_confirm (state, msg, msglen);
}
else
{
log_error ("%s message not expected by Initiator\n",
msgtypestr (msgtype));
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else if (!strcmp (rolestr, "Responder"))
{
if (msgtype == MSG_TYPE_DHPART2)
{
if (!(err = expect_state (msgtype, statestr, "DHPart1-sent")))
err = proc_msg_dhpart2 (state, msg, msglen);
}
else
{
log_error ("%s message not expected by Responder\n",
msgtypestr (msgtype));
err = gpg_error (GPG_ERR_INV_RESPONSE);
goto leave;
}
}
else
log_fatal ("invalid role '%s' in state file\n", rolestr);
leave:
xfree (msg);
nvc_release (state);
return err;
}
/* Return the keys for SESSIONIDSTR or the last one if it is NULL.
* Two keys are returned: The first is the one for sending encrypted
* data and the second one for decrypting received data. The keys are
* always returned hex encoded and both are terminated by a LF. */
static gpg_error_t
command_get (const char *sessionidstr)
{
gpg_error_t err;
unsigned char sessid[8];
nvc_t state;
if (!sessionidstr)
{
log_error ("calling without session-id is not yet implemented\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("invalid session id given\n");
goto leave;
}
set_session_id (sessid, sizeof sessid);
err = read_state (&state);
if (err)
{
log_error ("reading state of session %s failed: %s\n",
sessionidstr, gpg_strerror (err));
goto leave;
}
leave:
return err;
}
/* Cleanup command. */
static gpg_error_t
command_cleanup (void)
{
expire_old_states ();
return 0;
}
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index e6dc91d21..da3b49a2b 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1,4204 +1,4204 @@
/* gpgconf-comp.c - Configuration utility for GnuPG.
* Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
* Copyright (C) 2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GnuPG; if not, see <https://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <ctype.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN 1
# include <windows.h>
#else
# include <pwd.h>
# include <grp.h>
#endif
/* For log_logv(), asctimestamp(), gnupg_get_time (). */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/gc-opt-flags.h"
#include "gpgconf.h"
/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
returns a plain filename without escaping. As long as we have not
fixed that we need to use gpg2. */
#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
#define GPGNAME "gpg2"
#else
#define GPGNAME GPG_NAME
#endif
/* TODO:
Components: Add more components and their options.
Robustness: Do more validation. Call programs to do validation for us.
Add options to change backend binary path.
Extract binary path for some backends from gpgsm/gpg config.
*/
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ))
void gc_error (int status, int errnum, const char *fmt, ...) \
__attribute__ ((format (printf, 3, 4)));
#endif
/* Output a diagnostic message. If ERRNUM is not 0, then the output
is followed by a colon, a white space, and the error string for the
error number ERRNUM. In any case the output is finished by a
newline. The message is prepended by the program name, a colon,
and a whitespace. The output may be further formatted or
redirected by the jnlib logging facility. */
void
gc_error (int status, int errnum, const char *fmt, ...)
{
va_list arg_ptr;
va_start (arg_ptr, fmt);
log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr);
va_end (arg_ptr);
if (errnum)
log_printf (": %s\n", strerror (errnum));
else
log_printf ("\n");
if (status)
{
log_printf (NULL);
log_printf ("fatal error (exit status %i)\n", status);
gpgconf_failure (gpg_error_from_errno (errnum));
}
}
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
static void dirmngr_runtime_change (int killflag);
/* Backend configuration. Backends are used to decide how the default
and current value of an option can be determined, and how the
option can be changed. To every option in every component belongs
exactly one backend that controls and determines the option. Some
backends are programs from the GPG system. Others might be
implemented by GPGConf itself. If you change this enum, don't
forget to update GC_BACKEND below. */
typedef enum
{
/* Any backend, used for find_option (). */
GC_BACKEND_ANY,
/* The Gnu Privacy Guard. */
GC_BACKEND_GPG,
/* The Gnu Privacy Guard for S/MIME. */
GC_BACKEND_GPGSM,
/* The GPG Agent. */
GC_BACKEND_GPG_AGENT,
/* The GnuPG SCDaemon. */
GC_BACKEND_SCDAEMON,
/* The GnuPG directory manager. */
GC_BACKEND_DIRMNGR,
/* The LDAP server list file for the director manager. */
GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST,
/* The Pinentry (not a part of GnuPG, proper). */
GC_BACKEND_PINENTRY,
/* The number of the above entries. */
GC_BACKEND_NR
} gc_backend_t;
/* To be able to implement generic algorithms for the various
backends, we collect all information about them in this struct. */
static const struct
{
/* The name of the backend. */
const char *name;
/* The name of the program that acts as the backend. Some backends
don't have an associated program, but are implemented directly by
GPGConf. In this case, PROGRAM is NULL. */
char *program;
/* The module name (GNUPG_MODULE_NAME_foo) as defined by
../common/util.h. This value is used to get the actual installed
path of the program. 0 is used if no backend program is
available. */
char module_name;
/* The runtime change callback. If KILLFLAG is true the component
is killed and not just reloaded. */
void (*runtime_change) (int killflag);
/* The option name for the configuration filename of this backend.
This must be an absolute filename. It can be an option from a
different backend (but then ordering of the options might
matter). Note: This must be unique among all components. */
const char *option_config_filename;
/* If this is a file backend rather than a program backend, then
this is the name of the option associated with the file. */
const char *option_name;
} gc_backend[GC_BACKEND_NR] =
{
{ NULL }, /* GC_BACKEND_ANY dummy entry. */
{ GPG_DISP_NAME, GPGNAME, GNUPG_MODULE_NAME_GPG,
NULL, GPGCONF_NAME "-" GPG_NAME ".conf" },
{ GPGSM_DISP_NAME, GPGSM_NAME, GNUPG_MODULE_NAME_GPGSM,
NULL, GPGCONF_NAME "-" GPGSM_NAME ".conf" },
{ GPG_AGENT_DISP_NAME, GPG_AGENT_NAME, GNUPG_MODULE_NAME_AGENT,
gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" },
{ SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON,
scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" },
{ DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR,
dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" },
{ DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0,
NULL, "ldapserverlist-file", "LDAP Server" },
{ "Pinentry", "pinentry", GNUPG_MODULE_NAME_PINENTRY,
NULL, GPGCONF_NAME "-pinentry.conf" },
};
/* Option configuration. */
/* An option might take an argument, or not. Argument types can be
basic or complex. Basic types are generic and easy to validate.
Complex types provide more specific information about the intended
use, but can be difficult to validate. If you add to this enum,
don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE
NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL
INTERFACE. */
typedef enum
{
/* Basic argument types. */
/* No argument. */
GC_ARG_TYPE_NONE = 0,
/* A String argument. */
GC_ARG_TYPE_STRING = 1,
/* A signed integer argument. */
GC_ARG_TYPE_INT32 = 2,
/* An unsigned integer argument. */
GC_ARG_TYPE_UINT32 = 3,
/* ADD NEW BASIC TYPE ENTRIES HERE. */
/* Complex argument types. */
/* A complete filename. */
GC_ARG_TYPE_FILENAME = 32,
/* An LDAP server in the format
HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */
GC_ARG_TYPE_LDAP_SERVER = 33,
/* A 40 character fingerprint. */
GC_ARG_TYPE_KEY_FPR = 34,
/* A user ID or key ID or fingerprint for a certificate. */
GC_ARG_TYPE_PUB_KEY = 35,
/* A user ID or key ID or fingerprint for a certificate with a key. */
GC_ARG_TYPE_SEC_KEY = 36,
/* A alias list made up of a key, an equal sign and a space
separated list of values. */
GC_ARG_TYPE_ALIAS_LIST = 37,
/* ADD NEW COMPLEX TYPE ENTRIES HERE. */
/* The number of the above entries. */
GC_ARG_TYPE_NR
} gc_arg_type_t;
/* For every argument, we record some information about it in the
following struct. */
static const struct
{
/* For every argument type exists a basic argument type that can be
used as a fallback for input and validation purposes. */
gc_arg_type_t fallback;
/* Human-readable name of the type. */
const char *name;
} gc_arg_type[GC_ARG_TYPE_NR] =
{
/* The basic argument types have their own types as fallback. */
{ GC_ARG_TYPE_NONE, "none" },
{ GC_ARG_TYPE_STRING, "string" },
{ GC_ARG_TYPE_INT32, "int32" },
{ GC_ARG_TYPE_UINT32, "uint32" },
/* Reserved basic type entries for future extension. */
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
/* The complex argument types have a basic type as fallback. */
{ GC_ARG_TYPE_STRING, "filename" },
{ GC_ARG_TYPE_STRING, "ldap server" },
{ GC_ARG_TYPE_STRING, "key fpr" },
{ GC_ARG_TYPE_STRING, "pub key" },
{ GC_ARG_TYPE_STRING, "sec key" },
{ GC_ARG_TYPE_STRING, "alias list" },
};
/* Every option has an associated expert level, than can be used to
hide advanced and expert options from beginners. If you add to
this list, don't forget to update GC_LEVEL below. YOU MUST NOT
CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE
EXTERNAL INTERFACE. */
typedef enum
{
/* The basic options should always be displayed. */
GC_LEVEL_BASIC,
/* The advanced options may be hidden from beginners. */
GC_LEVEL_ADVANCED,
/* The expert options should only be displayed to experts. */
GC_LEVEL_EXPERT,
/* The invisible options should normally never be displayed. */
GC_LEVEL_INVISIBLE,
/* The internal options are never exported, they mark options that
are recorded for internal use only. */
GC_LEVEL_INTERNAL,
/* ADD NEW ENTRIES HERE. */
/* The number of the above entries. */
GC_LEVEL_NR
} gc_expert_level_t;
/* A description for each expert level. */
static const struct
{
const char *name;
} gc_level[] =
{
{ "basic" },
{ "advanced" },
{ "expert" },
{ "invisible" },
{ "internal" }
};
/* Option flags. The flags which are used by the backends are defined
by gc-opt-flags.h, included above.
YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
PART OF THE EXTERNAL INTERFACE. */
/* Some entries in the option list are not options, but mark the
beginning of a new group of options. These entries have the GROUP
flag set. */
#define GC_OPT_FLAG_GROUP (1UL << 0)
/* The ARG_OPT flag for an option indicates that the argument is
optional. This is never set for GC_ARG_TYPE_NONE options. */
#define GC_OPT_FLAG_ARG_OPT (1UL << 1)
/* The LIST flag for an option indicates that the option can occur
several times. A comma separated list of arguments is used as the
argument value. */
#define GC_OPT_FLAG_LIST (1UL << 2)
/* A human-readable description for each flag. */
static const struct
{
const char *name;
} gc_flag[] =
{
{ "group" },
{ "optional arg" },
{ "list" },
{ "runtime" },
{ "default" },
{ "default desc" },
{ "no arg desc" },
{ "no change" }
};
/* To each option, or group marker, the information in the GC_OPTION
struct is provided. If you change this, don't forget to update the
option list of each component. */
struct gc_option
{
/* If this is NULL, then this is a terminator in an array of unknown
length. Otherwise, if this entry is a group marker (see FLAGS),
then this is the name of the group described by this entry.
Otherwise it is the name of the option described by this
entry. The name must not contain a colon. */
const char *name;
/* The option flags. If the GROUP flag is set, then this entry is a
group marker, not an option, and only the fields LEVEL,
DESC_DOMAIN and DESC are valid. In all other cases, this entry
describes a new option and all fields are valid. */
unsigned long flags;
/* The expert level. This field is valid for options and groups. A
group has the expert level of the lowest-level option in the
group. */
gc_expert_level_t level;
/* A gettext domain in which the following description can be found.
If this is NULL, then DESC is not translated. Valid for groups
and options.
Note that we try to keep the description of groups within the
gnupg domain.
IMPORTANT: If you add a new domain please make sure to add a code
set switching call to the function my_dgettext further below. */
const char *desc_domain;
/* A gettext description for this group or option. If it starts
with a '|', then the string up to the next '|' describes the
argument, and the description follows the second '|'.
In general enclosing these description in N_() is not required
because the description should be identical to the one in the
help menu of the respective program. */
const char *desc;
/* The following fields are only valid for options. */
/* The type of the option argument. */
gc_arg_type_t arg_type;
/* The backend that implements this option. */
gc_backend_t backend;
/* The following fields are set to NULL at startup (because all
option's are declared as static variables). They are at the end
of the list so that they can be omitted from the option
declarations. */
/* This is true if the option is supported by this version of the
backend. */
int active;
/* The default value for this option. This is NULL if the option is
not present in the backend, the empty string if no default is
available, and otherwise a quoted string. */
char *default_value;
/* The default argument is only valid if the "optional arg" flag is
set, and specifies the default argument (value) that is used if
the argument is omitted. */
char *default_arg;
/* The current value of this option. */
char *value;
/* The new flags for this option. The only defined flag is actually
GC_OPT_FLAG_DEFAULT, and it means that the option should be
deleted. In this case, NEW_VALUE is NULL. */
unsigned long new_flags;
/* The new value of this option. */
char *new_value;
};
typedef struct gc_option gc_option_t;
/* Use this macro to terminate an option list. */
#define GC_OPTION_NULL { NULL }
#ifndef BUILD_WITH_AGENT
#define gc_options_gpg_agent NULL
#else
/* The options of the GC_COMPONENT_GPG_AGENT component. */
static gc_option_t gc_options_gpg_agent[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-" GPG_AGENT_NAME ".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not use the SCdaemon",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable ssh support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "ssh-fingerprint-digest",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "|ALGO|use ALGO to show ssh fingerprints",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "enable putty support",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg",
"|N|expire cached PINs after N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|expire SSH keys after N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum PIN cache lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set maximum SSH key lifetime to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED,
"gnupg", "allow passphrase to be prompted through Emacs",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-external-cache", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg", "disallow caller to override the pinentry",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Passphrase policy",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options enforcing a passphrase policy") },
{ "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow bypassing the passphrase policy"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|require at least N non-alpha characters for a new passphrase"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT,
"gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG_AGENT },
{ "max-passphrase-days", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|expire the passphrase after N days"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "enable-passphrase-history", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow the reuse of old passphrases"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "pinentry-timeout", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set the Pinentry timeout to N seconds"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_AGENT*/
#ifndef BUILD_WITH_SCDAEMON
#define gc_options_scdaemon NULL
#else
/* The options of the GC_COMPONENT_SCDAEMON component. */
static gc_option_t gc_options_scdaemon[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"SCDAEMON_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "reader-port", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|connect to reader at port N",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "ctapi-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as ct-API driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "pcsc-driver", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use NAME as PC/SC driver",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "disable-ccid", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not use the internal CCID driver",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "do not use a reader's pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "enable-pinpad-varlen",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "use variable length input for pinpad",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
{ "card-timeout", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "|N|disconnect the card after N seconds of inactivity",
GC_ARG_TYPE_UINT32, GC_BACKEND_SCDAEMON },
{ "application-priority",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LIST|Change the application priority to LIST",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
{ "log-file", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write a log to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_SCDAEMON },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "deny-admin", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
"gnupg", "deny the use of admin card commands",
GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_SCDAEMON*/
#ifndef BUILD_WITH_GPG
#define gc_options_gpg NULL
#else
/* The options of the GC_COMPONENT_GPG component. */
static gc_option_t gc_options_gpg[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPG_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
"gnupg", N_("|SPEC|set up email aliases"),
GC_ARG_TYPE_ALIAS_LIST, GC_BACKEND_GPG },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "trust-model",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPG },
/* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */
/* NULL, NULL, */
/* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("|URL|use keyserver at URL"), /* Deprecated - use dirmngr */
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("allow PKA lookups (DNS requests)"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
{ "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "no-auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL, GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
{ "max-cert-depth",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
{ "completes-needed",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
{ "marginals-needed",
GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPG*/
#ifndef BUILD_WITH_GPGSM
#define gc_options_gpgsm NULL
#else
/* The options of the GC_COMPONENT_GPGSM component. */
static gc_option_t gc_options_gpgsm[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"GPGSM_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|use NAME as default secret key"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|NAME|encrypt to user ID NAME as well"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "use system's dirmngr if available",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("disable all access to the dirmngr"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"),
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("|SPEC|use this keyserver to lookup keys"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_GPGSM },
{ "default_pubkey_algo",
(GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_NO_CHANGE), GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"gnupg", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_GPGSM },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM },
{ "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") },
{ "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "never consult a CRL",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", N_("do not check CRLs for root certificates"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "check validity using OCSP",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|N|number of certificates to include",
GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM },
{ "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not check certificate policies",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", "fetch missing issuer certificates",
GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
{ "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "|NAME|use cipher algorithm NAME",
GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_GPGSM*/
#ifndef BUILD_WITH_DIRMNGR
#define gc_options_dirmngr NULL
#else
/* The options of the GC_COMPONENT_DIRMNGR component. */
static gc_option_t gc_options_dirmngr[] =
{
/* The configuration file to which we write the changes. */
{ GPGCONF_NAME"-"DIRMNGR_NAME".conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "Monitor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the diagnostic output") },
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"dirmngr", "verbose",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "be somewhat more quiet",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Format",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the format of the output") },
{ "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "sh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "csh-style command output",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Configuration",
GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"dirmngr", "|FILE|read options from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_INT32, GC_BACKEND_DIRMNGR },
{ "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options useful for debugging") },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
"dirmngr", "|LEVEL|set the debugging level to LEVEL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not detach from the console",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", N_("|FILE|write server mode logs to FILE"),
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
{ "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
NULL, NULL,
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "Enforcement",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the interactivity and enforcement") },
{ "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "run without asking a user",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "force loading of outdated CRLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow online software version check",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Tor",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the use of Tor") },
{ "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "route all network traffic via Tor",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "Keyserver",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration for Keyservers") },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"gnupg", N_("|URL|use keyserver at URL"),
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "HTTP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for HTTP servers") },
{ "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of HTTP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore HTTP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|redirect all HTTP requests to URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", N_("use system's HTTP proxy setting"),
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "LDAP",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Configuration of LDAP servers to use") },
{ "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "inhibit the use of LDAP",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore LDAP CRL distribution points",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|HOST|use HOST for LDAP queries",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "do not use fallback hosts with --ldap-proxy",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "add new servers discovered in CRL distribution points"
" to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|set LDAP timeout to N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
/* The following entry must not be removed, as it is required for
the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */
{ "ldapserverlist-file",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
"dirmngr", "|FILE|read LDAP server list from FILE",
GC_ARG_TYPE_FILENAME, GC_BACKEND_DIRMNGR },
/* This entry must come after at least one entry for
GC_BACKEND_DIRMNGR in this component, so that the entry for
"ldapserverlist-file will be initialized before this one. */
{ "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
"gnupg", N_("LDAP server list"),
GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST },
{ "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "|N|do not return more than N items in one query",
GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR },
{ "OCSP",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Configuration for OCSP") },
{ "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
"dirmngr", "allow sending OCSP requests",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "ignore certificate contained OCSP service URLs",
GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
{ "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|URL|use OCSP responder at URL",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
{ "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"dirmngr", "|FPR|OCSP response signed by FPR",
GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
GC_OPTION_NULL
};
#endif /*BUILD_WITH_DIRMNGR*/
/* The options of the GC_COMPONENT_PINENTRY component. */
static gc_option_t gc_options_pinentry[] =
{
/* A dummy option to allow gc_component_list_components to find the
pinentry backend. Needs to be a conf file. */
{ GPGCONF_NAME"-pinentry.conf",
GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL,
NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_PINENTRY },
GC_OPTION_NULL
};
/* The information associated with each component. */
static const struct
{
/* The name of this component. Must not contain a colon (':')
character. */
const char *name;
/* The gettext domain for the description DESC. If this is NULL,
then the description is not translated. */
const char *desc_domain;
/* The description for this domain. */
const char *desc;
/* The list of options for this component, terminated by
GC_OPTION_NULL. */
gc_option_t *options;
} gc_component[] =
{
{ "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg },
{ "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent },
{ "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon },
{ "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm },
{ "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr },
{ "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry }
};
/* Structure used to collect error output of the backend programs. */
struct error_line_s;
typedef struct error_line_s *error_line_t;
struct error_line_s
{
error_line_t next; /* Link to next item. */
const char *fname; /* Name of the config file (points into BUFFER). */
unsigned int lineno; /* Line number of the config file. */
const char *errtext; /* Text of the error message (points into BUFFER). */
char buffer[1]; /* Helper buffer. */
};
/* Initialization and finalization. */
static void
gc_option_free (gc_option_t *o)
{
if (o == NULL || o->name == NULL)
return;
xfree (o->value);
gc_option_free (o + 1);
}
static void
gc_components_free (void)
{
int i;
for (i = 0; i < DIM (gc_component); i++)
gc_option_free (gc_component[i].options);
}
void
gc_components_init (void)
{
atexit (gc_components_free);
}
/* Engine specific support. */
static void
gpg_agent_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[5];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "--no-autostart";
argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[1], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
scdaemon_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[9];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
int i = 0;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "-s";
argv[i++] = "--no-autostart";
argv[i++] = "GETINFO scd_running";
argv[i++] = "/if ${! $?}";
argv[i++] = "scd killscd";
argv[i++] = "/end";
argv[i++] = NULL;
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[4], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
static void
dirmngr_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[6];
pid_t pid = (pid_t)(-1);
char *abs_homedir = NULL;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[0] = "--no-autostart";
argv[1] = "--dirmngr";
argv[2] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR";
if (gnupg_default_homedir_p ())
argv[3] = NULL;
else
{
argv[3] = "--homedir";
argv[4] = gnupg_homedir ();
argv[5] = NULL;
}
if (!err)
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[2], gpg_strerror (err));
gnupg_release_process (pid);
xfree (abs_homedir);
}
/* Launch the gpg-agent or the dirmngr if not already running. */
gpg_error_t
gc_component_launch (int component)
{
gpg_error_t err;
const char *pgmname;
const char *argv[5];
int i;
pid_t pid;
if (component < 0)
{
err = gc_component_launch (GC_COMPONENT_GPG_AGENT);
if (!err)
err = gc_component_launch (GC_COMPONENT_DIRMNGR);
return err;
}
if (!(component == GC_COMPONENT_GPG_AGENT
|| component == GC_COMPONENT_DIRMNGR))
{
log_error ("%s\n", _("Component not suitable for launching"));
gpgconf_failure (0);
}
if (gc_component_check_options (component, NULL, NULL))
{
log_error (_("Configuration file of component %s is broken\n"),
gc_component[component].name);
if (!opt.quiet)
log_info (_("Note: Use the command \"%s%s\" to get details.\n"),
gc_component[component].name, " --gpgconf-test");
gpgconf_failure (0);
}
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
i = 0;
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
if (component == GC_COMPONENT_DIRMNGR)
argv[i++] = "--dirmngr";
argv[i++] = "NOP";
argv[i] = NULL;
err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
if (!err)
err = gnupg_wait_process (pgmname, pid, 1, NULL);
if (err)
gc_error (0, 0, "error running '%s%s%s': %s",
pgmname,
component == GC_COMPONENT_DIRMNGR? " --dirmngr":"",
" NOP",
gpg_strerror (err));
gnupg_release_process (pid);
return err;
}
/* Unconditionally restart COMPONENT. */
void
gc_component_kill (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the restart for the selected backends. */
for (backend = GC_BACKEND_NR-1; backend; backend--)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (1);
}
}
/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */
void
gc_component_reload (int component)
{
int runtime[GC_BACKEND_NR];
gc_option_t *option;
gc_backend_t backend;
/* Set a flag for the backends to be reloaded. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
runtime[backend] = 0;
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
}
else
{
assert (component < GC_COMPONENT_NR);
option = gc_component[component].options;
for (; option && option->name; option++)
runtime[option->backend] = 1;
}
/* Do the reload for all selected backends. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
}
/* More or less Robust version of dgettext. It has the side effect of
switching the codeset to utf-8 because this is what we want to
output. In theory it is possible to keep the original code set and
- switch back for regular disgnostic output (redefine "_(" for that)
- but given the natur of this tool, being something invoked from
- other pograms, it does not make much sense. */
+ switch back for regular diagnostic output (redefine "_(" for that)
+ but given the nature of this tool, being something invoked from
+ other programs, it does not make much sense. */
static const char *
my_dgettext (const char *domain, const char *msgid)
{
#ifdef USE_SIMPLE_GETTEXT
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
gettext_use_utf8 (1);
}
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
/* FIXME: we have no dgettext, thus we can't switch. */
text = (char*)gettext (msgid);
return text ? text : msgid;
}
else
return msgid;
#elif defined(ENABLE_NLS)
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
bind_textdomain_codeset (PACKAGE_GT, "utf-8");
bindtextdomain (DIRMNGR_NAME, LOCALEDIR);
bind_textdomain_codeset (DIRMNGR_NAME, "utf-8");
}
/* Note: This is a hack to actually use the gnupg2 domain as
long we are in a transition phase where gnupg 1.x and 1.9 may
coexist. */
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
text = dgettext (domain, msgid);
return text ? text : msgid;
}
else
return msgid;
#else
(void)domain;
return msgid;
#endif
}
/* Percent-Escape special characters. The string is valid until the
next invocation of the function. */
char *
gc_percent_escape (const char *src)
{
static char *esc_str;
static int esc_str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (esc_str_len < new_len)
{
char *new_esc_str = realloc (esc_str, new_len);
if (!new_esc_str)
gc_error (1, errno, "can not escape string");
esc_str = new_esc_str;
esc_str_len = new_len;
}
dst = esc_str;
while (*src)
{
if (*src == '%')
{
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = '5';
}
else if (*src == ':')
{
/* The colon is used as field separator. */
*(dst++) = '%';
*(dst++) = '3';
*(dst++) = 'a';
}
else if (*src == ',')
{
/* The comma is used as list separator. */
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = 'c';
}
else if (*src == '\n')
{
/* The newline is problematic in a line-based format. */
*(dst++) = '%';
*(dst++) = '0';
*(dst++) = 'a';
}
else
*(dst++) = *(src);
src++;
}
*dst = '\0';
return esc_str;
}
/* Percent-Deescape special characters. The string is valid until the
next invocation of the function. */
static char *
percent_deescape (const char *src)
{
static char *str;
static int str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (str_len < new_len)
{
char *new_str = realloc (str, new_len);
if (!new_str)
gc_error (1, errno, "can not deescape string");
str = new_str;
str_len = new_len;
}
dst = str;
while (*src)
{
if (*src == '%')
{
int val = hextobyte (src + 1);
if (val < 0)
gc_error (1, 0, "malformed end of string %s", src);
*(dst++) = (char) val;
src += 3;
}
else
*(dst++) = *(src++);
}
*dst = '\0';
return str;
}
/* List all components that are available. */
void
gc_component_list_components (estream_t out)
{
gc_component_t component;
gc_option_t *option;
gc_backend_t backend;
int backend_seen[GC_BACKEND_NR];
const char *desc;
const char *pgmname;
for (component = 0; component < GC_COMPONENT_NR; component++)
{
option = gc_component[component].options;
if (option)
{
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
pgmname = "";
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program
&& !gc_backend[backend].module_name)
continue;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
break;
}
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fprintf (out, "%s\n", gc_percent_escape (pgmname));
}
}
}
static int
all_digits_p (const char *p, size_t len)
{
if (!len)
return 0; /* No. */
for (; len; len--, p++)
if (!isascii (*p) || !isdigit (*p))
return 0; /* No. */
return 1; /* Yes. */
}
/* Collect all error lines from stream FP. Only lines prefixed with
TAG are considered. Returns a list of error line items (which may
be empty). There is no error return. */
static error_line_t
collect_error_output (estream_t fp, const char *tag)
{
char buffer[1024];
char *p, *p2, *p3;
int c, cont_line;
unsigned int pos;
error_line_t eitem, errlines, *errlines_tail;
size_t taglen = strlen (tag);
errlines = NULL;
errlines_tail = &errlines;
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (cont_line)
; /*Ignore continuations of previous line. */
else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':')
{
/* "gpgsm: foo:4: bla" */
/* Yep, we are interested in this line. */
p = buffer + taglen + 1;
while (*p == ' ' || *p == '\t')
p++;
trim_trailing_spaces (p); /* Get rid of extra CRs. */
if (!*p)
; /* Empty lines are ignored. */
else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
&& all_digits_p (p2+1, p3 - (p2+1)))
{
/* Line in standard compiler format. */
p3++;
while (*p3 == ' ' || *p3 == '\t')
p3++;
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = eitem->buffer;
eitem->buffer[p2-p] = 0;
eitem->errtext = eitem->buffer + (p3 - p);
/* (we already checked that there are only ascii
digits followed by a colon) */
eitem->lineno = 0;
for (p2++; isdigit (*p2); p2++)
eitem->lineno = eitem->lineno*10 + (*p2 - '0');
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
else
{
/* Other error output. */
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = NULL;
eitem->errtext = eitem->buffer;
eitem->lineno = 0;
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
}
pos = 0;
/* If this was not a complete line mark that we are in a
continuation. */
cont_line = (c != '\n');
}
}
/* We ignore error lines not terminated by a LF. */
return errlines;
}
/* Check the options of a single component. If CONF_FILE is NULL the
* standard config file is used. If OUT is not NULL the output is
* written to that stream. Returns 0 if everything is OK. */
int
gc_component_check_options (int component, estream_t out, const char *conf_file)
{
gpg_error_t err;
unsigned int result;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
const char *pgmname;
const char *argv[4];
int i;
pid_t pid;
int exitcode;
estream_t errfp;
error_line_t errlines;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
option = gc_component[component].options;
for (; option && option->name; option++)
{
if ((option->flags & GC_OPT_FLAG_GROUP))
continue;
backend = option->backend;
if (backend_seen[backend])
continue;
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (!gc_backend[backend].program)
continue;
if (!gc_backend[backend].module_name)
continue;
break;
}
if (! option || ! option->name)
return 0;
pgmname = gnupg_module_name (gc_backend[backend].module_name);
i = 0;
if (conf_file)
{
argv[i++] = "--options";
argv[i++] = conf_file;
}
if (component == GC_COMPONENT_PINENTRY)
argv[i++] = "--version";
else
argv[i++] = "--gpgconf-test";
argv[i++] = NULL;
result = 0;
errlines = NULL;
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, NULL, &errfp, &pid);
if (err)
result |= 1; /* Program could not be run. */
else
{
errlines = collect_error_output (errfp,
gc_component[component].name);
if (gnupg_wait_process (pgmname, pid, 1, &exitcode))
{
if (exitcode == -1)
result |= 1; /* Program could not be run or it
terminated abnormally. */
result |= 2; /* Program returned an error. */
}
gnupg_release_process (pid);
es_fclose (errfp);
}
/* If the program could not be run, we can't tell whether
the config file is good. */
if (result & 1)
result |= 2;
if (out)
{
const char *desc;
error_line_t errptr;
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].name, gc_percent_escape (desc));
es_fputs (gc_percent_escape (pgmname), out);
es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
for (errptr = errlines; errptr; errptr = errptr->next)
{
if (errptr != errlines)
es_fputs ("\n:::::", out); /* Continuation line. */
if (errptr->fname)
es_fputs (gc_percent_escape (errptr->fname), out);
es_putc (':', out);
if (errptr->fname)
es_fprintf (out, "%u", errptr->lineno);
es_putc (':', out);
es_fputs (gc_percent_escape (errptr->errtext), out);
es_putc (':', out);
}
es_putc ('\n', out);
}
while (errlines)
{
error_line_t tmp = errlines->next;
xfree (errlines);
errlines = tmp;
}
return result;
}
/* Check all components that are available. */
void
gc_check_programs (estream_t out)
{
gc_component_t component;
for (component = 0; component < GC_COMPONENT_NR; component++)
gc_component_check_options (component, out, NULL);
}
/* Find the component with the name NAME. Returns -1 if not
found. */
int
gc_component_find (const char *name)
{
gc_component_t idx;
for (idx = 0; idx < GC_COMPONENT_NR; idx++)
{
if (gc_component[idx].options
&& !strcmp (name, gc_component[idx].name))
return idx;
}
return -1;
}
/* List the option OPTION. */
static void
list_one_option (const gc_option_t *option, estream_t out)
{
const char *desc = NULL;
char *arg_name = NULL;
if (option->desc)
{
desc = my_dgettext (option->desc_domain, option->desc);
if (*desc == '|')
{
const char *arg_tail = strchr (&desc[1], '|');
if (arg_tail)
{
int arg_len = arg_tail - &desc[1];
arg_name = xmalloc (arg_len + 1);
memcpy (arg_name, &desc[1], arg_len);
arg_name[arg_len] = '\0';
desc = arg_tail + 1;
}
}
}
/* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS
PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY
FIELDS. */
/* The name field. */
es_fprintf (out, "%s", option->name);
/* The flags field. */
es_fprintf (out, ":%lu", option->flags);
if (opt.verbose)
{
es_putc (' ', out);
if (!option->flags)
es_fprintf (out, "none");
else
{
unsigned long flags = option->flags;
unsigned long flag = 0;
unsigned long first = 1;
while (flags)
{
if (flags & 1)
{
if (first)
first = 0;
else
es_putc (',', out);
es_fprintf (out, "%s", gc_flag[flag].name);
}
flags >>= 1;
flag++;
}
}
}
/* The level field. */
es_fprintf (out, ":%u", option->level);
if (opt.verbose)
es_fprintf (out, " %s", gc_level[option->level].name);
/* The description field. */
es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : "");
/* The type field. */
es_fprintf (out, ":%u", option->arg_type);
if (opt.verbose)
es_fprintf (out, " %s", gc_arg_type[option->arg_type].name);
/* The alternate type field. */
es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback);
if (opt.verbose)
es_fprintf (out, " %s",
gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
/* The argument name field. */
es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : "");
xfree (arg_name);
/* The default value field. */
es_fprintf (out, ":%s", option->default_value ? option->default_value : "");
/* The default argument field. */
es_fprintf (out, ":%s", option->default_arg ? option->default_arg : "");
/* The value field. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST)
&& option->value)
/* The special format "1,1,1,1,...,1" is converted to a number
here. */
es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
else
es_fprintf (out, ":%s", option->value ? option->value : "");
/* ADD NEW FIELDS HERE. */
es_putc ('\n', out);
}
/* List all options of the component COMPONENT. */
void
gc_component_list_options (int component, estream_t out)
{
const gc_option_t *option = gc_component[component].options;
while (option && option->name)
{
/* Do not output unknown or internal options. */
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& (!option->active || option->level == GC_LEVEL_INTERNAL))
{
option++;
continue;
}
if (option->flags & GC_OPT_FLAG_GROUP)
{
const gc_option_t *group_option = option + 1;
gc_expert_level_t level = GC_LEVEL_NR;
/* The manual states that the group level is always the
minimum of the levels of all contained options. Due to
different active options, and because it is hard to
maintain manually, we calculate it here. The value in
the global static table is ignored. */
while (group_option->name)
{
if (group_option->flags & GC_OPT_FLAG_GROUP)
break;
if (group_option->level < level)
level = group_option->level;
group_option++;
}
/* Check if group is empty. */
if (level != GC_LEVEL_NR)
{
gc_option_t opt_copy;
/* Fix up the group level. */
memcpy (&opt_copy, option, sizeof (opt_copy));
opt_copy.level = level;
list_one_option (&opt_copy, out);
}
}
else
list_one_option (option, out);
option++;
}
}
/* Find the option NAME in component COMPONENT, for the backend
BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */
static gc_option_t *
find_option (gc_component_t component, const char *name,
gc_backend_t backend)
{
gc_option_t *option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& !strcmp (option->name, name)
&& (backend == GC_BACKEND_ANY || option->backend == backend))
break;
option++;
}
return option->name ? option : NULL;
}
/* Determine the configuration filename for the component COMPONENT
and backend BACKEND. */
static char *
get_config_filename (gc_component_t component, gc_backend_t backend)
{
char *filename = NULL;
gc_option_t *option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
assert (option);
assert (option->arg_type == GC_ARG_TYPE_FILENAME);
assert (!(option->flags & GC_OPT_FLAG_LIST));
if (!option->active || !option->default_value)
gc_error (1, 0, "Option %s, needed by backend %s, was not initialized",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
if (option->value && *option->value)
filename = percent_deescape (&option->value[1]);
else if (option->default_value && *option->default_value)
filename = percent_deescape (&option->default_value[1]);
else
filename = "";
#if HAVE_W32CE_SYSTEM
if (!(filename[0] == '/' || filename[0] == '\\'))
#elif defined(HAVE_DOSISH_SYSTEM)
if (!(filename[0]
&& filename[1] == ':'
&& (filename[2] == '/' || filename[2] == '\\')) /* x:\ or x:/ */
&& !((filename[0] == '\\' && filename[1] == '\\')
|| (filename[0] == '/' && filename[1] == '/'))) /* \\server */
#else
if (filename[0] != '/')
#endif
gc_error (1, 0, "Option %s, needed by backend %s, is not absolute",
gc_backend[backend].option_config_filename,
gc_backend[backend].name);
return filename;
}
/* Retrieve the options for the component COMPONENT from backend
* BACKEND, which we already know is a program-type backend. With
* ONLY_INSTALLED set components which are not installed are silently
* ignored. */
static void
retrieve_options_from_program (gc_component_t component, gc_backend_t backend,
int only_installed)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
int exitcode;
pid_t pid;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t config;
char *config_filename;
pgmname = (gc_backend[backend].module_name
? gnupg_module_name (gc_backend[backend].module_name)
: gc_backend[backend].program );
argv[0] = "--gpgconf-list";
argv[1] = NULL;
if (only_installed && access (pgmname, X_OK))
{
return; /* The component is not installed. */
}
err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
NULL, &outfp, NULL, &pid);
if (err)
{
gc_error (1, 0, "could not gather active options from '%s': %s",
pgmname, gpg_strerror (err));
}
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
gc_option_t *option;
char *linep;
unsigned long flags = 0;
char *default_value = NULL;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s from %s",
line, pgmname);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s from %s",
line, pgmname);
linep = end;
}
/* Extract default value, if present. Default to empty if
not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
if (flags & GC_OPT_FLAG_DEFAULT)
default_value = linep;
linep = end;
}
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
if (option->active)
gc_error (1, errno, "option %s returned twice from %s",
line, pgmname);
option->active = 1;
option->flags |= flags;
if (default_value && *default_value)
option->default_value = xstrdup (default_value);
}
}
if (length < 0 || es_ferror (outfp))
gc_error (1, errno, "error reading from %s", pgmname);
if (es_fclose (outfp))
gc_error (1, errno, "error closing %s", pgmname);
err = gnupg_wait_process (pgmname, pid, 1, &exitcode);
if (err)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
gnupg_release_process (pid);
/* At this point, we can parse the configuration file. */
config_filename = get_config_filename (component, backend);
config = es_fopen (config_filename, "r");
if (!config)
{
if (errno != ENOENT)
gc_error (0, errno, "warning: can not open config file %s",
config_filename);
}
else
{
while ((length = es_read_line (config, &line, &line_len, NULL)) > 0)
{
char *name;
char *value;
gc_option_t *option;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
value = name;
while (*value && *value != ' ' && *value != '\t'
&& *value != '#' && *value != '\r' && *value != '\n')
value++;
if (*value == ' ' || *value == '\t')
{
char *end;
*(value++) = '\0';
while (*value == ' ' || *value == '\t')
value++;
end = value;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
while (end > value && (end[-1] == ' ' || end[-1] == '\t'))
end--;
*end = '\0';
}
else
*value = '\0';
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line, backend);
if (option)
{
char *opt_value;
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
if (*value)
gc_error (0, 0,
"warning: ignoring argument %s for option %s",
value, name);
opt_value = xstrdup ("1");
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
opt_value = xasprintf ("\"%s", gc_percent_escape (value));
else
{
/* FIXME: Verify that the number is sane. */
opt_value = xstrdup (value);
}
/* Now enter the option into the table. */
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (option->value)
xfree (option->value);
option->value = opt_value;
}
else
{
if (!option->value)
option->value = opt_value;
else
{
char *old = option->value;
option->value = xasprintf ("%s,%s", old, opt_value);
xfree (old);
xfree (opt_value);
}
}
}
}
if (length < 0 || es_ferror (config))
gc_error (1, errno, "error reading from %s", config_filename);
if (es_fclose (config))
gc_error (1, errno, "error closing %s", config_filename);
}
xfree (line);
}
/* Retrieve the options for the component COMPONENT from backend
BACKEND, which we already know is of type file list. */
static void
retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
{
gc_option_t *list_option;
gc_option_t *config_option;
char *list_filename;
gpgrt_stream_t list_file;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
char *list = NULL;
list_option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (list_option);
assert (!list_option->active);
list_filename = get_config_filename (component, backend);
list_file = gpgrt_fopen (list_filename, "r");
if (!list_file)
gc_error (0, errno, "warning: can not open list file %s", list_filename);
else
{
while ((length = gpgrt_read_line (list_file, &line, &line_len, NULL)) > 0)
{
char *start;
char *end;
char *new_list;
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (!*start || *start == '#' || *start == '\r' || *start == '\n')
continue;
end = start;
while (*end && *end != '#' && *end != '\r' && *end != '\n')
end++;
/* Walk back to skip trailing white spaces. Looks evil, but
works because of the conditions on START and END imposed
at this point (END is at least START + 1, and START is
not a whitespace character). */
while (*(end - 1) == ' ' || *(end - 1) == '\t')
end--;
*end = '\0';
/* FIXME: Oh, no! This is so lame! Should use realloc and
really append. */
if (list)
{
new_list = xasprintf ("%s,\"%s", list, gc_percent_escape (start));
xfree (list);
list = new_list;
}
else
list = xasprintf ("\"%s", gc_percent_escape (start));
}
if (length < 0 || gpgrt_ferror (list_file))
gc_error (1, errno, "can not read list file %s", list_filename);
}
list_option->active = 1;
list_option->value = list;
/* Fix up the read-only flag. */
config_option = find_option
(component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY);
if (config_option->flags & GC_OPT_FLAG_NO_CHANGE)
list_option->flags |= GC_OPT_FLAG_NO_CHANGE;
if (list_file && gpgrt_fclose (list_file))
gc_error (1, errno, "error closing %s", list_filename);
xfree (line);
}
/* Retrieve the currently active options and their defaults from all
involved backends for this component. Using -1 for component will
retrieve all options from all installed components. */
void
gc_component_retrieve_options (int component)
{
int process_all = 0;
int backend_seen[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0;
if (component == -1)
{
process_all = 1;
component = 0;
assert (component < GC_COMPONENT_NR);
}
do
{
if (component == GC_COMPONENT_PINENTRY)
continue; /* Skip this dummy component. */
option = gc_component[component].options;
while (option && option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP))
{
backend = option->backend;
if (backend_seen[backend])
{
option++;
continue;
}
backend_seen[backend] = 1;
assert (backend != GC_BACKEND_ANY);
if (gc_backend[backend].program)
retrieve_options_from_program (component, backend,
process_all);
else
retrieve_options_from_file (component, backend);
}
option++;
}
}
while (process_all && ++component < GC_COMPONENT_NR);
}
/* Perform a simple validity check based on the type. Return in
* NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
* type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode
* is used. */
static void
option_check_validity (gc_option_t *option, unsigned long flags,
char *new_value, unsigned long *new_value_nr,
int verbatim)
{
char *arg;
if (!option->active)
gc_error (1, 0, "option %s not supported by backend %s",
option->name, gc_backend[option->backend].name);
if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name);
if (flags & GC_OPT_FLAG_DEFAULT)
{
if (*new_value)
gc_error (1, 0, "argument %s provided for deleted option %s",
new_value, option->name);
return;
}
/* GC_ARG_TYPE_NONE options have special list treatment. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
char *tail;
gpg_err_set_errno (0);
*new_value_nr = strtoul (new_value, &tail, 0);
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*tail)
gc_error (1, 0, "garbage after argument for option %s",
option->name);
if (!(option->flags & GC_OPT_FLAG_LIST))
{
if (*new_value_nr != 1)
gc_error (1, 0, "argument for non-list option %s of type 0 "
"(none) must be 1", option->name);
}
else
{
if (*new_value_nr == 0)
gc_error (1, 0, "argument for option %s of type 0 (none) "
"must be positive", option->name);
}
return;
}
arg = new_value;
do
{
if (*arg == '\0' || (*arg == ',' && !verbatim))
{
if (!(option->flags & GC_OPT_FLAG_ARG_OPT))
gc_error (1, 0, "argument required for option %s", option->name);
if (*arg == ',' && !verbatim && !(option->flags & GC_OPT_FLAG_LIST))
gc_error (1, 0, "list found for non-list option %s", option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
{
if (*arg != '"' && !verbatim)
gc_error (1, 0, "string argument for option %s must begin "
"with a quote (\") character", option->name);
/* FIXME: We do not allow empty string arguments for now, as
we do not quote arguments in configuration files, and
thus no argument is indistinguishable from the empty
string. */
if (arg[1] == '\0' || (arg[1] == ',' && !verbatim))
gc_error (1, 0, "empty string argument for option %s is "
"currently not allowed. Please report this!",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
{
long res;
gpg_err_set_errno (0);
res = strtol (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32)
{
unsigned long res;
gpg_err_set_errno (0);
res = strtoul (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
arg = verbatim? strchr (arg, ',') : NULL;
if (arg)
arg++;
}
while (arg && *arg);
}
#ifdef HAVE_W32_SYSTEM
int
copy_file (const char *src_name, const char *dst_name)
{
#define BUF_LEN 4096
char buffer[BUF_LEN];
int len;
gpgrt_stream_t src;
gpgrt_stream_t dst;
src = gpgrt_fopen (src_name, "r");
if (src == NULL)
return -1;
dst = gpgrt_fopen (dst_name, "w");
if (dst == NULL)
{
int saved_err = errno;
gpgrt_fclose (src);
gpg_err_set_errno (saved_err);
return -1;
}
do
{
int written;
len = gpgrt_fread (buffer, 1, BUF_LEN, src);
if (len == 0)
break;
written = gpgrt_fwrite (buffer, 1, len, dst);
if (written != len)
break;
}
while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst));
if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src))
{
int saved_errno = errno;
gpgrt_fclose (src);
gpgrt_fclose (dst);
unlink (dst_name);
gpg_err_set_errno (saved_errno);
return -1;
}
if (gpgrt_fclose (dst))
gc_error (1, errno, "error closing %s", dst_name);
if (gpgrt_fclose (src))
gc_error (1, errno, "error closing %s", src_name);
return 0;
}
#endif /* HAVE_W32_SYSTEM */
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. This
* function may store pointers to malloced strings in SRC_FILENAMEP,
* DEST_FILENAMEP, and ORIG_FILENAMEP. Those must be freed by the
* caller. The strings refer to three versions of the configuration
* file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_file (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
char *arg;
char *cur_arg = NULL;
option = find_option (component,
gc_backend[backend].option_name, GC_BACKEND_ANY);
assert (option);
assert (option->active);
assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE);
/* FIXME. Throughout the function, do better error reporting. */
/* Note that get_config_filename() calls percent_deescape(), so we
call this before processing the arguments. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
arg = option->new_value;
if (arg && arg[0] == '\0')
arg = NULL;
else if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_file_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char *endp;
char saved_end;
endp = start;
end = endp;
/* Search for the end of the line. */
while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n')
{
endp++;
if (*endp && *endp != ' ' && *endp != '\t'
&& *endp != '\r' && *endp != '\n' && *endp != '#')
end = endp + 1;
}
saved_end = *end;
*end = '\0';
if ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| !cur_arg || strcmp (start, cur_arg))
disable = 1;
else
{
/* Find next argument. */
if (arg)
{
char *arg_end;
arg++;
arg_end = strchr (arg, ',');
if (arg_end)
*arg_end = '\0';
cur_arg = percent_deescape (arg);
if (arg_end)
{
*arg_end = ',';
arg = arg_end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
*end = saved_end;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the arguments we are going to add.
Now, dump the new arguments and write the end marker, possibly
followed by the rest of the original file. */
while (cur_arg)
{
gpgrt_fprintf (src_file, "%s\n", cur_arg);
/* Find next argument. */
if (arg)
{
char *end;
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
cur_arg = percent_deescape (arg);
if (end)
{
*end = ',';
arg = end + 1;
}
else
arg = NULL;
}
else
cur_arg = NULL;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_file_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_file_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_file_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_file_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Create and verify the new configuration file for the specified
* backend and component. Returns 0 on success and -1 on error. If
* VERBATIM is set the profile mode is used. This function may store
* pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and
* ORIG_FILENAMEP. Those must be freed by the caller. The strings
* refer to three versions of the configuration file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_program (gc_component_t component, gc_backend_t backend,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep,
int verbatim)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
/* Special hack for gpg, see below. */
int utf8strings_seen = 0;
/* FIXME. Throughout the function, do better error reporting. */
dest_filename = xstrdup (get_config_filename (component, backend));
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL. */
fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
else if (backend == GC_BACKEND_GPG && in_marker
&& ! strcmp ("utf8-strings\n", line))
{
/* Strip duplicated entries. */
if (utf8strings_seen)
disable = 1;
else
utf8strings_seen = 1;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char saved_end;
end = start;
while (*end && *end != ' ' && *end != '\t'
&& *end != '\r' && *end != '\n' && *end != '#')
end++;
saved_end = *end;
*end = '\0';
option = find_option (component, start, backend);
*end = saved_end;
if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| option->new_value))
disable = 1;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the options we are going to change.
Now, dump the changed options (except for those we are going to
revert to their default), and write the end marker, possibly
followed by the rest of the original file. */
/* We have to turn on UTF8 strings for GnuPG. */
if (backend == GC_BACKEND_GPG && ! utf8strings_seen)
gpgrt_fprintf (src_file, "utf8-strings\n");
option = gc_component[component].options;
while (option->name)
{
if (!(option->flags & GC_OPT_FLAG_GROUP)
&& option->backend == backend
&& option->new_value)
{
char *arg = option->new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_NONE)
{
assert (*arg == '1');
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
arg++;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
{
char *end;
if (!verbatim)
{
log_assert (*arg == '"');
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
}
else
end = NULL;
gpgrt_fprintf (src_file, "%s %s\n", option->name,
verbatim? arg : percent_deescape (arg));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
else
{
char *end;
end = strchr (arg, ',');
if (end)
*end = '\0';
gpgrt_fprintf (src_file, "%s %s\n", option->name, arg);
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
assert (arg == NULL || *arg == '\0' || *arg == ',');
if (arg && *arg == ',')
arg++;
}
while (arg && *arg);
}
option++;
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Common code for gc_component_change_options and
* gc_process_gpgconf_conf. If VERBATIM is set the profile parsing
* mode is used. */
static void
change_one_value (gc_option_t *option, int *runtime,
unsigned long flags, char *new_value, int verbatim)
{
unsigned long new_value_nr = 0;
option_check_validity (option, flags, new_value, &new_value_nr, verbatim);
if (option->flags & GC_OPT_FLAG_RUNTIME)
runtime[option->backend] = 1;
option->new_flags = flags;
if (!(flags & GC_OPT_FLAG_DEFAULT))
{
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& (option->flags & GC_OPT_FLAG_LIST))
{
char *str;
/* We convert the number to a list of 1's for convenient
list handling. */
assert (new_value_nr > 0);
option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
str = option->new_value;
*(str++) = '1';
while (--new_value_nr > 0)
{
*(str++) = ',';
*(str++) = '1';
}
*(str++) = '\0';
}
else
option->new_value = xstrdup (new_value);
}
}
/* Read the modifications from IN and apply them. If IN is NULL the
modifications are expected to already have been set to the global
table. If VERBATIM is set the profile mode is used. */
void
gc_component_change_options (int component, estream_t in, estream_t out,
int verbatim)
{
int err = 0;
int block = 0;
int runtime[GC_BACKEND_NR];
char *src_filename[GC_BACKEND_NR];
char *dest_filename[GC_BACKEND_NR];
char *orig_filename[GC_BACKEND_NR];
gc_backend_t backend;
gc_option_t *option;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy component for now. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
runtime[backend] = 0;
src_filename[backend] = NULL;
dest_filename[backend] = NULL;
orig_filename[backend] = NULL;
}
if (in)
{
/* Read options from the file IN. */
while ((length = es_read_line (in, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *new_value = "";
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s", line);
linep = end;
}
/* Don't allow setting of the no change flag. */
flags &= ~GC_OPT_FLAG_NO_CHANGE;
/* Extract default value, if present. Default to empty if not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
new_value = linep;
linep = end;
}
option = find_option (component, line, GC_BACKEND_ANY);
if (!option)
gc_error (1, 0, "unknown option %s", line);
if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
{
gc_error (0, 0, "ignoring new value for option %s",
option->name);
continue;
}
change_one_value (option, runtime, flags, new_value, 0);
}
if (length < 0 || gpgrt_ferror (in))
gc_error (1, errno, "error reading stream 'in'");
}
/* Now that we have collected and locally verified the changes,
write them out to new configuration files, verify them
externally, and then commit them. */
option = gc_component[component].options;
while (option && option->name)
{
/* Go on if we have already seen this backend, or if there is
nothing to do. */
if (src_filename[option->backend]
|| !(option->new_flags || option->new_value))
{
option++;
continue;
}
if (gc_backend[option->backend].program)
{
err = change_options_program (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend],
verbatim);
if (! err)
{
/* External verification. */
err = gc_component_check_options (component, out,
src_filename[option->backend]);
if (err)
{
gc_error (0, 0,
_("External verification of component %s failed"),
gc_component[component].name);
gpg_err_set_errno (EINVAL);
}
}
}
else
err = change_options_file (component, option->backend,
&src_filename[option->backend],
&dest_filename[option->backend],
&orig_filename[option->backend]);
if (err)
break;
option++;
}
/* We are trying to atomically commit all changes. Unfortunately,
we cannot rely on gnupg_rename_file to manage the signals for us,
doing so would require us to pass NULL as BLOCK to any subsequent
call to it. Instead, we just manage the signal handling
manually. */
block = 1;
gnupg_block_all_signals ();
if (! err && ! opt.dry_run)
{
int i;
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* FIXME: Make a verification here. */
assert (dest_filename[i]);
if (orig_filename[i])
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
else
{
#ifdef HAVE_W32_SYSTEM
/* We skip the unlink if we expect the file not to
be there. */
err = gnupg_rename_file (src_filename[i], dest_filename[i], NULL);
#else /* HAVE_W32_SYSTEM */
/* This is a bit safer than rename() because we
expect DEST_FILENAME not to be there. If it
happens to be there, this will fail. */
err = link (src_filename[i], dest_filename[i]);
if (!err)
err = unlink (src_filename[i]);
#endif /* !HAVE_W32_SYSTEM */
}
if (err)
break;
xfree (src_filename[i]);
src_filename[i] = NULL;
}
}
}
if (err || opt.dry_run)
{
int i;
int saved_errno = errno;
/* An error occurred or a dry-run is requested. */
for (i = 0; i < GC_BACKEND_NR; i++)
{
if (src_filename[i])
{
/* The change was not yet committed. */
unlink (src_filename[i]);
if (orig_filename[i])
unlink (orig_filename[i]);
}
else
{
/* The changes were already committed. FIXME: This is a
tad dangerous, as we don't know if we don't overwrite
a version of the file that is even newer than the one
we just installed. */
if (orig_filename[i])
gnupg_rename_file (orig_filename[i], dest_filename[i], NULL);
else
unlink (dest_filename[i]);
}
}
if (err)
gc_error (1, saved_errno, "could not commit changes");
/* Fall-through for dry run. */
goto leave;
}
/* If it all worked, notify the daemons of the changes. */
if (opt.runtime)
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
if (runtime[backend] && gc_backend[backend].runtime_change)
(*gc_backend[backend].runtime_change) (0);
}
/* Move the per-process backup file into its place. */
for (backend = 0; backend < GC_BACKEND_NR; backend++)
if (orig_filename[backend])
{
char *backup_filename;
assert (dest_filename[backend]);
backup_filename = xasprintf ("%s.%s.bak",
dest_filename[backend], GPGCONF_NAME);
gnupg_rename_file (orig_filename[backend], backup_filename, NULL);
xfree (backup_filename);
}
leave:
if (block)
gnupg_unblock_all_signals ();
xfree (line);
for (backend = 0; backend < GC_BACKEND_NR; backend++)
{
xfree (src_filename[backend]);
xfree (dest_filename[backend]);
xfree (orig_filename[backend]);
}
}
/* Check whether USER matches the current user of one of its group.
This function may change USER. Returns true is there is a
match. */
static int
key_matches_user_or_group (char *user)
{
char *group;
if (*user == '*' && user[1] == 0)
return 1; /* A single asterisk matches all users. */
group = strchr (user, ':');
if (group)
*group++ = 0;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we don't support groups. */
if (group && *group)
gc_error (0, 0, _("Note that group specifications are ignored\n"));
#ifndef HAVE_W32CE_SYSTEM
if (*user)
{
static char *my_name;
if (!my_name)
{
char tmp[1];
DWORD size = 1;
GetUserNameA (tmp, &size);
my_name = xmalloc (size);
if (!GetUserNameA (my_name, &size))
gc_error (1,0, "error getting current user name: %s",
w32_strerror (-1));
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
#endif /*HAVE_W32CE_SYSTEM*/
#else /*!HAVE_W32_SYSTEM*/
/* First check whether the user matches. */
if (*user)
{
static char *my_name;
if (!my_name)
{
struct passwd *pw = getpwuid ( getuid () );
if (!pw)
gc_error (1, errno, "getpwuid failed for current user");
my_name = xstrdup (pw->pw_name);
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
/* If that failed, check whether a group matches. */
if (group && *group)
{
static char *my_group;
static char **my_supgroups;
int n;
if (!my_group)
{
struct group *gr = getgrgid ( getgid () );
if (!gr)
gc_error (1, errno, "getgrgid failed for current user");
my_group = xstrdup (gr->gr_name);
}
if (!strcmp (group, my_group))
return 1; /* Found. */
if (!my_supgroups)
{
int ngids;
gid_t *gids;
ngids = getgroups (0, NULL);
gids = xcalloc (ngids+1, sizeof *gids);
ngids = getgroups (ngids, gids);
if (ngids < 0)
gc_error (1, errno, "getgroups failed for current user");
my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
for (n=0; n < ngids; n++)
{
struct group *gr = getgrgid ( gids[n] );
if (!gr)
gc_error (1, errno, "getgrgid failed for supplementary group");
my_supgroups[n] = xstrdup (gr->gr_name);
}
xfree (gids);
}
for (n=0; my_supgroups[n]; n++)
if (!strcmp (group, my_supgroups[n]))
return 1; /* Found. */
}
#endif /*!HAVE_W32_SYSTEM*/
return 0; /* No match. */
}
/* Read and process the global configuration file for gpgconf. This
optional file is used to update our internal tables at runtime and
may also be used to set new default values. If FNAME is NULL the
default name will be used. With UPDATE set to true the internal
tables are actually updated; if not set, only a syntax check is
done. If DEFAULTS is true the global options are written to the
configuration files. If LISTFP is set, no changes are done but the
configuration file is printed to LISTFP in a colon separated format.
Returns 0 on success or if the config file is not present; -1 is
returned on error. */
int
gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
estream_t listfp)
{
int result = 0;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
gpgrt_stream_t config;
int lineno = 0;
int in_rule = 0;
int got_match = 0;
int runtime[GC_BACKEND_NR];
int backend_id, component_id;
char *fname;
if (fname_arg)
fname = xstrdup (fname_arg);
else
fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf",
NULL);
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
config = gpgrt_fopen (fname, "r");
if (!config)
{
/* Do not print an error if the file is not available, except
when running in syntax check mode. */
if (errno != ENOENT || !update)
{
gc_error (0, errno, "can not open global config file '%s'", fname);
result = -1;
}
xfree (fname);
return result;
}
while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0)
{
char *key, *component, *option, *flags, *value;
char *empty;
gc_option_t *option_info = NULL;
char *p;
int is_continuation;
lineno++;
key = line;
while (*key == ' ' || *key == '\t')
key++;
if (!*key || *key == '#' || *key == '\r' || *key == '\n')
continue;
is_continuation = (key != line);
/* Parse the key field. */
if (!is_continuation && got_match)
break; /* Finish after the first match. */
else if (!is_continuation)
{
in_rule = 0;
for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
;
if (!*p)
{
gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno);
result = -1;
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing rule",
GPG_ERR_SYNTAX, fname, lineno);
continue;
}
*p++ = 0;
component = p;
}
else if (!in_rule)
{
gc_error (0, 0, "continuation but no rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
else
{
component = key;
key = NULL;
}
in_rule = 1;
/* Parse the component. */
while (*component == ' ' || *component == '\t')
component++;
for (p=component; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == component)
{
gc_error (0, 0, "missing component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
" missing component",
GPG_ERR_NO_NAME, fname, lineno);
result = -1;
continue;
}
empty = p;
*p++ = 0;
option = p;
component_id = gc_component_find (component);
if (component_id < 0)
{
gc_error (0, 0, "unknown component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown component",
GPG_ERR_UNKNOWN_NAME, fname, lineno);
result = -1;
}
/* Parse the option name. */
while (*option == ' ' || *option == '\t')
option++;
for (p=option; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == option)
{
gc_error (0, 0, "missing option at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing option",
GPG_ERR_INV_NAME, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
flags = p;
if ( component_id != -1)
{
option_info = find_option (component_id, option, GC_BACKEND_ANY);
if (!option_info)
{
gc_error (0, 0, "unknown option at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown option",
GPG_ERR_UNKNOWN_OPTION, fname, lineno);
result = -1;
}
}
/* Parse the optional flags. */
while (*flags == ' ' || *flags == '\t')
flags++;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
gc_error (0, 0, "syntax error in rule at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"syntax error in rule",
GPG_ERR_SYNTAX, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
value = p;
}
else /* No flags given. */
{
value = flags;
flags = NULL;
}
/* Parse the optional value. */
while (*value == ' ' || *value == '\t')
value++;
for (p=value; *p && !strchr ("\r\n", *p); p++)
;
if (p == value)
value = empty; /* No value given; let it point to an empty string. */
else
{
/* Strip trailing white space. */
*p = 0;
for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
*p = 0;
}
/* Check flag combinations. */
if (!flags)
;
else if (!strcmp (flags, "default"))
{
if (*value)
{
gc_error (0, 0, "flag \"default\" may not be combined "
"with a value at '%s', line %d",
fname, lineno);
result = -1;
}
}
else if (!strcmp (flags, "change"))
;
else if (!strcmp (flags, "no-change"))
;
else
{
gc_error (0, 0, "unknown flag at '%s', line %d",
fname, lineno);
result = -1;
}
/* In list mode we print out all records. */
if (listfp && !result)
{
/* If this is a new ruleset, print a key record. */
if (!is_continuation)
{
char *group = strchr (key, ':');
if (group)
{
*group++ = 0;
if ((p = strchr (group, ':')))
*p = 0; /* We better strip any extra stuff. */
}
es_fprintf (listfp, "k:%s:", gc_percent_escape (key));
es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):"");
}
/* All other lines are rule records. */
es_fprintf (listfp, "r:::%s:%s:%s:",
gc_component[component_id].name,
option_info->name? option_info->name : "",
flags? flags : "");
if (value != empty)
es_fprintf (listfp, "\"%s", gc_percent_escape (value));
es_putc ('\n', listfp);
}
/* Check whether the key matches but do this only if we are not
running in syntax check mode. */
if ( update
&& !result && !listfp
&& (got_match || (key && key_matches_user_or_group (key))) )
{
int newflags = 0;
got_match = 1;
/* Apply the flags from gpgconf.conf. */
if (!flags)
;
else if (!strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
else if (!strcmp (flags, "no-change"))
option_info->flags |= GC_OPT_FLAG_NO_CHANGE;
else if (!strcmp (flags, "change"))
option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE;
if (defaults)
{
/* Here we explicitly allow updating the value again. */
if (newflags)
{
option_info->new_flags = 0;
}
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 0);
}
}
}
if (length < 0 || gpgrt_ferror (config))
{
gc_error (0, errno, "error reading from '%s'", fname);
result = -1;
}
if (gpgrt_fclose (config))
gc_error (0, errno, "error closing '%s'", fname);
xfree (line);
/* If it all worked, process the options. */
if (!result && update && defaults && !listfp)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 0);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname);
return result;
}
/*
* Apply the profile FNAME to all known configure files.
*/
gpg_error_t
gc_apply_profile (const char *fname)
{
gpg_error_t err;
char *fname_buffer = NULL;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t fp;
int lineno = 0;
int runtime[GC_BACKEND_NR];
int backend_id;
int component_id = -1;
int skip_section = 0;
int error_count = 0;
int newflags;
if (!fname)
fname = "-";
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
runtime[backend_id] = 0;
if (!(!strcmp (fname, "-")
|| strchr (fname, '/')
#ifdef HAVE_W32_SYSTEM
|| strchr (fname, '\\')
#endif
|| strchr (fname, '.')))
{
/* FNAME looks like a standard profile name. Check whether one
* is installed and use that instead of the given file name. */
fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S,
fname, ".prf", NULL);
if (!access (fname_buffer, F_OK))
fname = fname_buffer;
}
fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info ("applying profile '%s'\n", fname);
err = 0;
while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0)
{
char *name, *flags, *value;
gc_option_t *option_info = NULL;
char *p;
lineno++;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
trim_trailing_spaces (name);
/* Check whether this is a new section. */
if (*name == '[')
{
name++;
skip_section = 0;
/* New section: Get the name of the component. */
p = strchr (name, ']');
if (!p)
{
error_count++;
log_info ("%s:%d:%d: error: syntax error in section tag\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
*p++ = 0;
if (*p)
log_info ("%s:%d:%d: warning: garbage after section tag\n",
fname, lineno, (int)(p - line));
trim_spaces (name);
component_id = gc_component_find (name);
if (component_id < 0)
{
log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n",
fname, lineno, (int)(name - line), name );
skip_section = 1;
}
continue;
}
if (skip_section)
continue;
if (component_id < 0)
{
error_count++;
log_info ("%s:%d:%d: error: not in a valid section\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
/* Parse the option name. */
for (p = name; *p && !spacep (p); p++)
;
*p++ = 0;
value = p;
option_info = find_option (component_id, name, GC_BACKEND_ANY);
if (!option_info)
{
error_count++;
log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n",
fname, lineno, (int)(name - line),
name, gc_component[component_id].name);
continue;
}
/* Parse the optional flags. */
trim_spaces (value);
flags = value;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
log_info ("%s:%d:%d: warning: invalid flag specification\n",
fname, lineno, (int)(p - line));
continue;
}
*p++ = 0;
value = p;
trim_spaces (value);
}
else /* No flags given. */
flags = NULL;
/* Set required defaults. */
if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE
&& !*value)
value = "1";
/* Check and save this option. */
newflags = 0;
if (flags && !strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
if (newflags)
option_info->new_flags = 0;
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (option_info, runtime, newflags, value, 1);
}
if (length < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
error_count++;
log_error (_("%s:%u: read error: %s\n"),
fname, lineno, gpg_strerror (err));
}
if (es_fclose (fp))
log_error (_("error closing '%s'\n"), fname);
if (error_count)
log_error (_("error parsing '%s'\n"), fname);
xfree (line);
/* If it all worked, process the options. */
if (!err)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 1);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
(*gc_backend[backend_id].runtime_change) (0);
}
}
xfree (fname_buffer);
return err;
}
diff --git a/tools/gpgconf.c b/tools/gpgconf.c
index 6dd3c0c9c..44d916bc2 100644
--- a/tools/gpgconf.c
+++ b/tools/gpgconf.c
@@ -1,925 +1,925 @@
/* gpgconf.c - Configuration utility for GnuPG
* Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 2016 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpgconf.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/status.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oVerbose = 'v',
oRuntime = 'r',
oComponent = 'c',
oNull = '0',
oNoVerbose = 500,
oHomedir,
oBuilddir,
oStatusFD,
oShowSocket,
aListComponents,
aCheckPrograms,
aListOptions,
aChangeOptions,
aCheckOptions,
aApplyDefaults,
aListConfig,
aCheckConfig,
aQuerySWDB,
aListDirs,
aLaunch,
aKill,
aCreateSocketDir,
aRemoveSocketDir,
aApplyProfile,
aReload
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] =
{
{ 300, NULL, 0, N_("@Commands:\n ") },
{ aListComponents, "list-components", 256, N_("list all components") },
{ aCheckPrograms, "check-programs", 256, N_("check all programs") },
{ aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
{ aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") },
{ aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") },
{ aApplyDefaults, "apply-defaults", 256,
N_("apply global default values") },
{ aApplyProfile, "apply-profile", 256,
N_("|FILE|update configuration files using FILE") },
{ aListDirs, "list-dirs", 256,
N_("get the configuration directories for @GPGCONF@") },
{ aListConfig, "list-config", 256,
N_("list global configuration file") },
{ aCheckConfig, "check-config", 256,
N_("check global configuration file") },
{ aQuerySWDB, "query-swdb", 256,
N_("query the software version database") },
{ aReload, "reload", 256, N_("reload all or a given component")},
{ aLaunch, "launch", 256, N_("launch a given component")},
{ aKill, "kill", 256, N_("kill a given component")},
{ aCreateSocketDir, "create-socketdir", 256, "@"},
{ aRemoveSocketDir, "remove-socketdir", 256, "@"},
{ 301, NULL, 0, N_("@\nOptions:\n ") },
{ oOutput, "output", 2, N_("use as output file") },
{ oVerbose, "verbose", 0, N_("verbose") },
{ oQuiet, "quiet", 0, N_("quiet") },
{ oDryRun, "dry-run", 0, N_("do not make any changes") },
{ oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") },
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
/* hidden options */
{ oHomedir, "homedir", 2, "@" },
{ oBuilddir, "build-prefix", 2, "@" },
{ oNull, "null", 0, "@" },
{ oNoVerbose, "no-verbose", 0, "@"},
ARGPARSE_s_n (oShowSocket, "show-socket", "@"),
ARGPARSE_end(),
};
/* The stream to output the status information. Status Output is disabled if
* this is NULL. */
static estream_t statusfp;
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 11: p = "@GPGCONF@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGCONF@ [options]\n"
"Manage configuration options for tools of the @GNUPG@ system\n");
break;
default: p = NULL; break;
}
return p;
}
/* Return the fp for the output. This is usually stdout unless
--output has been used. In the latter case this function opens
that file. */
static estream_t
get_outfp (estream_t *fp)
{
if (!*fp)
{
if (opt.outfile)
{
*fp = es_fopen (opt.outfile, "w");
if (!*fp)
gc_error (1, errno, "can not open '%s'", opt.outfile);
}
else
*fp = es_stdout;
}
return *fp;
}
/* Set the status FD. */
static void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
gpgconf_write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc (' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
}
static void
list_dirs (estream_t fp, char **names)
{
static struct {
const char *name;
const char *(*fnc)(void);
const char *extra;
} list[] = {
{ "sysconfdir", gnupg_sysconfdir, NULL },
{ "bindir", gnupg_bindir, NULL },
{ "libexecdir", gnupg_libexecdir, NULL },
{ "libdir", gnupg_libdir, NULL },
{ "datadir", gnupg_datadir, NULL },
{ "localedir", gnupg_localedir, NULL },
{ "socketdir", gnupg_socketdir, NULL },
{ "dirmngr-socket", dirmngr_socket_name, NULL,},
{ "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME },
{ "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME },
{ "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME },
{ "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME },
{ "homedir", gnupg_homedir, NULL }
};
int idx, j;
char *tmp;
const char *s;
for (idx = 0; idx < DIM (list); idx++)
{
s = list[idx].fnc ();
if (list[idx].extra)
{
tmp = make_filename (s, list[idx].extra, NULL);
s = tmp;
}
else
tmp = NULL;
if (!names)
es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s));
else
{
for (j=0; names[j]; j++)
if (!strcmp (names[j], list[idx].name))
{
es_fputs (s, fp);
es_putc (opt.null? '\0':'\n', fp);
}
}
xfree (tmp);
}
#ifdef HAVE_W32_SYSTEM
tmp = read_w32_registry_string (NULL,
GNUPG_REGISTRY_DIR,
"HomeDir");
if (tmp)
{
es_fflush (fp);
log_info ("Warning: homedir taken from registry key (%s %s)\n",
GNUPG_REGISTRY_DIR, "HomeDir");
xfree (tmp);
}
#endif /*HAVE_W32_SYSTEM*/
}
/* Check whether NAME is valid argument for query_swdb(). Valid names
* start with a letter and contain only alphanumeric characters or an
* underscore. */
static int
valid_swdb_name_p (const char *name)
{
if (!name || !*name || !alphap (name))
return 0;
for (name++; *name; name++)
if (!alnump (name) && *name != '_')
return 0;
return 1;
}
/* Query the SWDB file. If necessary and possible this functions asks
* the dirmngr to load an updated version of that file. The caller
* needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and
* optional the currently installed version in CURRENT_VERSION. The
* output written to OUT is a colon delimited line with these fields:
*
* name :: The name of the package
* curvers:: The installed version if given.
* status :: This value tells the status of the software package
* '-' :: No information available
* (error or CURRENT_VERSION not given)
* '?' :: Unknown NAME
* 'u' :: Update available
* 'c' :: The version is Current
* 'n' :: The current version is already Newer than the
* available one.
* urgency :: If the value is greater than zero an urgent update is required.
* error :: 0 on success or an gpg_err_code_t
* Common codes seen:
* GPG_ERR_TOO_OLD :: The SWDB file is to old to be used.
* GPG_ERR_ENOENT :: The SWDB file is not available.
- * GPG_ERR_BAD_SIGNATURE :: Currupted SWDB file.
+ * GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file.
* filedate:: Date of the swdb file (yyyymmddThhmmss)
* verified:: Date we checked the validity of the file (yyyyymmddThhmmss)
* version :: The version string from the swdb.
* reldate :: Release date of that version (yyyymmddThhmmss)
* size :: Size of the package in bytes.
* hash :: SHA-2 hash of the package.
*
*/
static void
query_swdb (estream_t out, const char *name, const char *current_version)
{
gpg_error_t err;
const char *search_name;
char *fname = NULL;
estream_t fp = NULL;
char *line = NULL;
char *self_version = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char *fields[2];
char *p;
gnupg_isotime_t filedate = {0};
gnupg_isotime_t verified = {0};
char *value_ver = NULL;
gnupg_isotime_t value_date = {0};
char *value_size = NULL;
char *value_sha2 = NULL;
unsigned long value_size_ul = 0;
int status, i;
if (!valid_swdb_name_p (name))
{
log_error ("error in package name '%s': %s\n",
name, gpg_strerror (GPG_ERR_INV_NAME));
goto leave;
}
if (!strcmp (name, "gnupg"))
search_name = GNUPG_SWDB_TAG;
else if (!strcmp (name, "gnupg1"))
search_name = "gnupg1";
else
search_name = name;
if (!current_version && !strcmp (name, "gnupg"))
{
/* Use our own version but string a possible beta string. */
self_version = xstrdup (PACKAGE_VERSION);
p = strchr (self_version, '-');
if (p)
*p = 0;
current_version = self_version;
}
if (current_version && (strchr (current_version, ':')
|| compare_version_strings (current_version, NULL)))
{
log_error ("error in version string '%s': %s\n",
current_version, gpg_strerror (GPG_ERR_INV_ARG));
goto leave;
}
fname = make_filename (gnupg_homedir (), "swdb.lst", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Note that the parser uses the first occurrence of a matching
* values and ignores possible duplicated values. */
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (split_fields (line, fields, DIM (fields)) < DIM(fields))
continue; /* Skip empty lines and names w/o a value. */
if (*fields[0] == '#')
continue; /* Skip comments. */
/* Record the meta data. */
if (!*filedate && !strcmp (fields[0], ".filedate"))
{
string2isotime (filedate, fields[1]);
continue;
}
if (!*verified && !strcmp (fields[0], ".verified"))
{
string2isotime (verified, fields[1]);
continue;
}
/* Tokenize the name. */
p = strrchr (fields[0], '_');
if (!p)
continue; /* Name w/o an underscore. */
*p++ = 0;
/* Wait for the requested name. */
if (!strcmp (fields[0], search_name))
{
if (!strcmp (p, "ver") && !value_ver)
value_ver = xstrdup (fields[1]);
else if (!strcmp (p, "date") && !*value_date)
string2isotime (value_date, fields[1]);
else if (!strcmp (p, "size") && !value_size)
value_size = xstrdup (fields[1]);
else if (!strcmp (p, "sha2") && !value_sha2)
value_sha2 = xstrdup (fields[1]);
}
}
if (len < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
if (!*filedate || !*verified)
{
err = gpg_error (GPG_ERR_INV_TIME);
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
goto leave;
}
if (!value_ver)
{
es_fprintf (out, "%s:%s:?:::::::::\n",
name,
current_version? current_version : "");
goto leave;
}
if (value_size)
{
gpg_err_set_errno (0);
value_size_ul = strtoul (value_size, &p, 10);
if (errno)
value_size_ul = 0;
else if (*p == 'k')
value_size_ul *= 1024;
}
err = 0;
status = '-';
if (compare_version_strings (value_ver, NULL))
err = gpg_error (GPG_ERR_INV_VALUE);
else if (!current_version)
;
else if (!(i = compare_version_strings (value_ver, current_version)))
status = 'c';
else if (i > 0)
status = 'u';
else
status = 'n';
es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n",
name,
current_version? current_version : "",
status,
err,
filedate,
verified,
value_ver,
value_date,
value_size_ul,
value_sha2? value_sha2 : "");
leave:
xfree (value_ver);
xfree (value_size);
xfree (value_sha2);
xfree (line);
es_fclose (fp);
xfree (fname);
xfree (self_version);
}
/* gpgconf main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
ARGPARSE_ARGS pargs;
const char *fname;
int no_more_options = 0;
enum cmd_and_opt_values cmd = 0;
estream_t outfp = NULL;
int show_socket = 0;
early_system_init ();
gnupg_reopen_std (GPGCONF_NAME);
set_strusage (my_strusage);
log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gc_components_init ();
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = 1; /* Do not remove the args. */
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oRuntime:
opt.runtime = 1;
break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break;
case oNull: opt.null = 1; break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oShowSocket: show_socket = 1; break;
case aListDirs:
case aListComponents:
case aCheckPrograms:
case aListOptions:
case aChangeOptions:
case aCheckOptions:
case aApplyDefaults:
case aApplyProfile:
case aListConfig:
case aCheckConfig:
case aQuerySWDB:
case aReload:
case aLaunch:
case aKill:
case aCreateSocketDir:
case aRemoveSocketDir:
cmd = pargs.r_opt;
break;
default: pargs.err = 2; break;
}
}
if (log_get_errorcount (0))
gpgconf_failure (GPG_ERR_USER_2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
fname = argc ? *argv : NULL;
switch (cmd)
{
case aListComponents:
default:
/* List all components. */
gc_component_list_components (get_outfp (&outfp));
break;
case aCheckPrograms:
/* Check all programs. */
gc_check_programs (get_outfp (&outfp));
break;
case aListOptions:
case aChangeOptions:
case aCheckOptions:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else
{
int idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
if (cmd == aCheckOptions)
gc_component_check_options (idx, get_outfp (&outfp), NULL);
else
{
gc_component_retrieve_options (idx);
if (gc_process_gpgconf_conf (NULL, 1, 0, NULL))
gpgconf_failure (0);
if (cmd == aListOptions)
gc_component_list_options (idx, get_outfp (&outfp));
else if (cmd == aChangeOptions)
gc_component_change_options (idx, es_stdin,
get_outfp (&outfp), 0);
}
}
break;
case aLaunch:
case aKill:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else if (!strcmp (fname, "all"))
{
if (cmd == aLaunch)
{
if (gc_component_launch (-1))
gpgconf_failure (0);
}
else
{
gc_component_kill (-1);
}
}
else
{
/* Launch/Kill a given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else if (cmd == aLaunch)
{
err = gc_component_launch (idx);
if (show_socket)
{
char *names[2];
if (idx == GC_COMPONENT_GPG_AGENT)
names[0] = "agent-socket";
else if (idx == GC_COMPONENT_DIRMNGR)
names[0] = "dirmngr-socket";
else
names[0] = NULL;
names[1] = NULL;
get_outfp (&outfp);
list_dirs (outfp, names);
}
if (err)
gpgconf_failure (0);
}
else
{
/* We don't error out if the kill failed because this
command should do nothing if the component is not
running. */
gc_component_kill (idx);
}
}
break;
case aReload:
if (!fname || !strcmp (fname, "all"))
{
/* Reload all. */
gc_component_reload (-1);
}
else
{
/* Reload given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else
{
gc_component_reload (idx);
}
}
break;
case aListConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp)))
gpgconf_failure (0);
break;
case aCheckConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, NULL))
gpgconf_failure (0);
break;
case aApplyDefaults:
if (fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("No argument allowed"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
gc_component_retrieve_options (-1);
if (gc_process_gpgconf_conf (NULL, 1, 1, NULL))
gpgconf_failure (0);
break;
case aApplyProfile:
gc_component_retrieve_options (-1);
if (gc_apply_profile (fname))
gpgconf_failure (0);
break;
case aListDirs:
/* Show the system configuration directories for gpgconf. */
get_outfp (&outfp);
list_dirs (outfp, argc? argv : NULL);
break;
case aQuerySWDB:
/* Query the software version database. */
if (!fname || argc > 2)
{
es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n",
GPGCONF_NAME);
gpgconf_failure (GPG_ERR_USER_2);
}
get_outfp (&outfp);
query_swdb (outfp, fname, argc > 1? argv[1] : NULL);
break;
case aCreateSocketDir:
{
char *socketdir;
unsigned int flags;
/* Make sure that the top /run/user/UID/gnupg dir has been
* created. */
gnupg_socketdir ();
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 64) && !opt.dry_run)
{
/* No sub dir - create it. */
if (gnupg_mkdir (socketdir, "-rwx"))
gc_error (1, errno, "error creating '%s'", socketdir);
/* Try again. */
xfree (socketdir);
socketdir = _gnupg_socketdir_internal (1, &flags);
}
/* Give some info. */
if ( (flags & ~32) || opt.verbose || opt.dry_run)
{
log_info ("socketdir is '%s'\n", socketdir);
if ((flags & 1)) log_info ("\tgeneral error\n");
if ((flags & 2)) log_info ("\tno /run/user dir\n");
if ((flags & 4)) log_info ("\tbad permissions\n");
if ((flags & 8)) log_info ("\tbad permissions (subdir)\n");
if ((flags & 16)) log_info ("\tmkdir failed\n");
if ((flags & 32)) log_info ("\tnon-default homedir\n");
if ((flags & 64)) log_info ("\tno such subdir\n");
if ((flags & 128)) log_info ("\tusing homedir as fallback\n");
}
if ((flags & ~32) && !opt.dry_run)
gc_error (1, 0, "error creating socket directory");
xfree (socketdir);
}
break;
case aRemoveSocketDir:
{
char *socketdir;
unsigned int flags;
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 128))
log_info ("ignoring request to remove non /run/user socket dir\n");
else if (opt.dry_run)
;
else if (rmdir (socketdir))
{
/* If the director is not empty we first try to delete
* socket files. */
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY
|| gpg_err_code (err) == GPG_ERR_EEXIST)
{
static const char * const names[] = {
GPG_AGENT_SOCK_NAME,
GPG_AGENT_EXTRA_SOCK_NAME,
GPG_AGENT_BROWSER_SOCK_NAME,
GPG_AGENT_SSH_SOCK_NAME,
SCDAEMON_SOCK_NAME,
DIRMNGR_SOCK_NAME
};
int i;
char *p;
for (i=0; i < DIM(names); i++)
{
p = strconcat (socketdir , "/", names[i], NULL);
if (p)
gnupg_remove (p);
xfree (p);
}
if (rmdir (socketdir))
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
else if (gpg_err_code (err) == GPG_ERR_ENOENT)
gc_error (0, 0, "warning: removing '%s' failed: %s",
socketdir, gpg_strerror (err));
else
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
xfree (socketdir);
}
break;
}
if (outfp != es_stdout)
if (es_fclose (outfp))
gc_error (1, errno, "error closing '%s'", opt.outfile);
if (log_get_errorcount (0))
gpgconf_failure (0);
else
gpgconf_write_status (STATUS_SUCCESS, NULL);
return 0;
}
void
gpgconf_failure (gpg_error_t err)
{
log_flush ();
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
gpgconf_write_status
(STATUS_FAILURE, "- %u",
gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err);
exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1);
}
diff --git a/tools/mime-parser.c b/tools/mime-parser.c
index 98f27105c..0db1a9c23 100644
--- a/tools/mime-parser.c
+++ b/tools/mime-parser.c
@@ -1,833 +1,833 @@
/* mime-parser.c - Parse MIME structures (high level rfc822 parser).
* Copyright (C) 2016 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/util.h"
#include "rfc822parse.h"
#include "mime-parser.h"
enum pgpmime_states
{
PGPMIME_NONE = 0,
PGPMIME_WAIT_ENCVERSION,
PGPMIME_IN_ENCVERSION,
PGPMIME_WAIT_ENCDATA,
PGPMIME_IN_ENCDATA,
PGPMIME_GOT_ENCDATA,
PGPMIME_WAIT_SIGNEDDATA,
PGPMIME_IN_SIGNEDDATA,
PGPMIME_WAIT_SIGNATURE,
PGPMIME_IN_SIGNATURE,
PGPMIME_GOT_SIGNATURE,
PGPMIME_INVALID
};
/* Definition of the mime parser object. */
struct mime_parser_context_s
{
void *cookie; /* Cookie passed to all callbacks. */
/* The callback to announce the transition from header to body. */
gpg_error_t (*t2body) (void *cookie, int level);
/* The callback to announce a new part. */
gpg_error_t (*new_part) (void *cookie,
const char *mediatype,
const char *mediasubtype);
/* The callback to return data of a part. */
gpg_error_t (*part_data) (void *cookie,
const void *data,
size_t datalen);
/* The callback to collect encrypted data. */
gpg_error_t (*collect_encrypted) (void *cookie, const char *data);
/* The callback to collect signed data. */
gpg_error_t (*collect_signeddata) (void *cookie, const char *data);
/* The callback to collect a signature. */
gpg_error_t (*collect_signature) (void *cookie, const char *data);
/* The RFC822 parser context is stored here during callbacks. */
rfc822parse_t msg;
/* Helper to convey error codes from user callbacks. */
gpg_error_t err;
int nesting_level; /* The current nesting level. */
int hashing_at_level; /* The nesting level at which we are hashing. */
enum pgpmime_states pgpmime; /* Current PGP/MIME state. */
unsigned int delay_hashing:1;/* Helper for PGPMIME_IN_SIGNEDDATA. */
unsigned int want_part:1; /* Return the current part. */
unsigned int decode_part:2; /* Decode the part. 1 = QP, 2 = Base64. */
unsigned int verbose:1; /* Enable verbose mode. */
unsigned int debug:1; /* Enable debug mode. */
/* Flags to help with debug output. */
struct {
unsigned int n_skip; /* Skip showing these number of lines. */
unsigned int header:1; /* Show the header lines. */
unsigned int data:1; /* Show the data lines. */
unsigned int as_note:1; /* Show the next data line as a note. */
unsigned int boundary : 1;
} show;
struct b64state *b64state; /* NULL or malloced Base64 decoder state. */
/* A buffer for reading a mail line, */
char line[5000];
};
/* Print the event received by the parser for debugging. */
static void
show_message_parser_event (rfc822parse_event_t event)
{
const char *s;
switch (event)
{
case RFC822PARSE_OPEN: s= "Open"; break;
case RFC822PARSE_CLOSE: s= "Close"; break;
case RFC822PARSE_CANCEL: s= "Cancel"; break;
case RFC822PARSE_T2BODY: s= "T2Body"; break;
case RFC822PARSE_FINISH: s= "Finish"; break;
case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break;
case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
default: s= "[unknown event]"; break;
}
log_debug ("*** RFC822 event %s\n", s);
}
/* Do in-place decoding of quoted-printable data of LENGTH in BUFFER.
Returns the new length of the buffer and stores true at R_SLBRK if
the line ended with a soft line break; false is stored if not.
- This function asssumes that a complete line is passed in
+ This function assumes that a complete line is passed in
buffer. */
static size_t
qp_decode (char *buffer, size_t length, int *r_slbrk)
{
char *d, *s;
if (r_slbrk)
*r_slbrk = 0;
/* Fixme: We should remove trailing white space first. */
for (s=d=buffer; length; length--)
{
if (*s == '=')
{
if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2))
{
s++;
*(unsigned char*)d++ = xtoi_2 (s);
s += 2;
length -= 2;
}
else if (length > 2 && s[1] == '\r' && s[2] == '\n')
{
/* Soft line break. */
s += 3;
length -= 2;
if (r_slbrk && length == 1)
*r_slbrk = 1;
}
else if (length > 1 && s[1] == '\n')
{
/* Soft line break with only a Unix line terminator. */
s += 2;
length -= 1;
if (r_slbrk && length == 1)
*r_slbrk = 1;
}
else if (length == 1)
{
/* Soft line break at the end of the line. */
s += 1;
if (r_slbrk)
*r_slbrk = 1;
}
else
*d++ = *s++;
}
else
*d++ = *s++;
}
return d - buffer;
}
/* This function is called by parse_mail to communicate events. This
* callback communicates with the caller using a structure passed in
* OPAQUE. Should return 0 on success or set ERRNO and return -1. */
static int
parse_message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg)
{
mime_parser_t ctx = opaque;
const char *s;
int rc = 0;
- /* Make the RFC822 parser context availabale for callbacks. */
+ /* Make the RFC822 parser context available for callbacks. */
ctx->msg = msg;
if (ctx->debug)
show_message_parser_event (event);
if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY)
{
/* We need to check here whether to start collecting signed data
* because attachments might come without header lines and thus
* we won't see the BEGIN_HEADER event. */
if (ctx->pgpmime == PGPMIME_WAIT_SIGNEDDATA)
{
if (ctx->debug)
log_debug ("begin_hash\n");
ctx->hashing_at_level = ctx->nesting_level;
ctx->pgpmime = PGPMIME_IN_SIGNEDDATA;
ctx->delay_hashing = 0;
}
}
if (event == RFC822PARSE_OPEN)
{
/* Initialize for a new message. */
ctx->show.header = 1;
}
else if (event == RFC822PARSE_T2BODY)
{
rfc822parse_field_t field;
ctx->want_part = 0;
ctx->decode_part = 0;
if (ctx->t2body)
{
rc = ctx->t2body (ctx->cookie, ctx->nesting_level);
if (rc)
goto t2body_leave;
}
field = rfc822parse_parse_field (msg, "Content-Type", -1);
if (field)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (field, &s2);
if (s1)
{
if (ctx->verbose)
log_debug ("h media: %*s%s %s\n",
ctx->nesting_level*2, "", s1, s2);
if (ctx->pgpmime == PGPMIME_WAIT_ENCVERSION)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "pgp-encrypted"))
{
if (ctx->debug)
log_debug ("c begin_encversion\n");
ctx->pgpmime = PGPMIME_IN_ENCVERSION;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/pgp-encrypted", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_WAIT_ENCDATA)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "octet-stream"))
{
if (ctx->debug)
log_debug ("c begin_encdata\n");
ctx->pgpmime = PGPMIME_IN_ENCDATA;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/octet-stream", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_WAIT_SIGNATURE)
{
if (!strcmp (s1, "application")
&& !strcmp (s2, "pgp-signature"))
{
if (ctx->debug)
log_debug ("c begin_signature\n");
ctx->pgpmime = PGPMIME_IN_SIGNATURE;
}
else
{
log_error ("invalid PGP/MIME structure;"
" expected '%s', got '%s/%s'\n",
"application/pgp-signature", s1, s2);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (!strcmp (s1, "multipart")
&& !strcmp (s2, "encrypted"))
{
s = rfc822parse_query_parameter (field, "protocol", 0);
if (s)
{
if (ctx->debug)
log_debug ("h encrypted.protocol: %s\n", s);
if (!strcmp (s, "application/pgp-encrypted"))
{
if (ctx->pgpmime)
log_error ("note: "
"ignoring nested PGP/MIME signature\n");
else
ctx->pgpmime = PGPMIME_WAIT_ENCVERSION;
}
else if (ctx->verbose)
log_debug ("# this protocol is not supported\n");
}
}
else if (!strcmp (s1, "multipart")
&& !strcmp (s2, "signed"))
{
s = rfc822parse_query_parameter (field, "protocol", 1);
if (s)
{
if (ctx->debug)
log_debug ("h signed.protocol: %s\n", s);
if (!strcmp (s, "application/pgp-signature"))
{
if (ctx->pgpmime)
log_error ("note: "
"ignoring nested PGP/MIME signature\n");
else
ctx->pgpmime = PGPMIME_WAIT_SIGNEDDATA;
}
else if (ctx->verbose)
log_debug ("# this protocol is not supported\n");
}
}
else if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, s1, s2);
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
else
{
if (ctx->debug)
log_debug ("h media: %*s none\n", ctx->nesting_level*2, "");
if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, "", "");
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
rfc822parse_release_field (field);
}
else
{
if (ctx->verbose)
log_debug ("h media: %*stext plain [assumed]\n",
ctx->nesting_level*2, "");
if (ctx->new_part)
{
ctx->err = ctx->new_part (ctx->cookie, "text", "plain");
if (!ctx->err)
ctx->want_part = 1;
else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE)
ctx->err = 0;
else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE)
{
ctx->want_part = ctx->decode_part = 1;
ctx->err = 0;
}
}
}
/* Figure out the encoding if needed. */
if (ctx->decode_part)
{
char *value;
size_t valueoff;
ctx->decode_part = 0; /* Fallback for unknown encoding. */
value = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1,
&valueoff);
if (value)
{
if (!stricmp (value+valueoff, "quoted-printable"))
ctx->decode_part = 1;
else if (!stricmp (value+valueoff, "base64"))
{
ctx->decode_part = 2;
if (ctx->b64state)
b64dec_finish (ctx->b64state); /* Reuse state. */
else
{
ctx->b64state = xtrymalloc (sizeof *ctx->b64state);
if (!ctx->b64state)
rc = gpg_error_from_syserror ();
}
if (!rc)
rc = b64dec_start (ctx->b64state, NULL);
}
free (value); /* Right, we need a plain free. */
}
}
t2body_leave:
ctx->show.header = 0;
ctx->show.data = 1;
ctx->show.n_skip = 1;
}
else if (event == RFC822PARSE_PREAMBLE)
ctx->show.as_note = 1;
else if (event == RFC822PARSE_LEVEL_DOWN)
{
if (ctx->debug)
log_debug ("b down\n");
ctx->nesting_level++;
}
else if (event == RFC822PARSE_LEVEL_UP)
{
if (ctx->debug)
log_debug ("b up\n");
if (ctx->nesting_level)
ctx->nesting_level--;
else
log_error ("invalid structure (bad nesting level)\n");
}
else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY)
{
ctx->show.data = 0;
ctx->show.boundary = 1;
if (event == RFC822PARSE_BOUNDARY)
{
ctx->show.header = 1;
ctx->show.n_skip = 1;
if (ctx->debug)
log_debug ("b part\n");
}
else if (ctx->debug)
log_debug ("b last\n");
if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
{
if (ctx->debug)
log_debug ("c end_encdata\n");
ctx->pgpmime = PGPMIME_GOT_ENCDATA;
/* FIXME: We should assert (event == LAST_BOUNDARY). */
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA
&& ctx->nesting_level == ctx->hashing_at_level)
{
if (ctx->debug)
log_debug ("c end_hash\n");
ctx->pgpmime = PGPMIME_WAIT_SIGNATURE;
if (ctx->collect_signeddata)
ctx->err = ctx->collect_signeddata (ctx->cookie, NULL);
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
{
if (ctx->debug)
log_debug ("c end_signature\n");
ctx->pgpmime = PGPMIME_GOT_SIGNATURE;
/* FIXME: We should assert (event == LAST_BOUNDARY). */
}
else if (ctx->want_part)
{
if (ctx->part_data)
{
/* FIXME: We may need to flush things. */
ctx->err = ctx->part_data (ctx->cookie, NULL, 0);
}
ctx->want_part = 0;
}
}
ctx->msg = NULL;
return rc;
}
/* Create a new mime parser object. COOKIE is a values which will be
* used as first argument for all callbacks registered with this
* parser object. */
gpg_error_t
mime_parser_new (mime_parser_t *r_parser, void *cookie)
{
mime_parser_t ctx;
*r_parser = NULL;
ctx = xtrycalloc (1, sizeof *ctx);
if (!ctx)
return gpg_error_from_syserror ();
ctx->cookie = cookie;
*r_parser = ctx;
return 0;
}
/* Release a mime parser object. */
void
mime_parser_release (mime_parser_t ctx)
{
if (!ctx)
return;
if (ctx->b64state)
{
b64dec_finish (ctx->b64state);
xfree (ctx->b64state);
}
xfree (ctx);
}
/* Set verbose and debug mode. */
void
mime_parser_set_verbose (mime_parser_t ctx, int level)
{
if (!level)
{
ctx->verbose = 0;
ctx->debug = 0;
}
else
{
ctx->verbose = 1;
if (level > 10)
ctx->debug = 1;
}
}
/* Set a callback for the transition from header to body. LEVEL is
* the current nesting level, starting with 0. This callback can be
* used to evaluate headers before any other action is done. Note
* that if a new NEW_PART callback needs to be called it is done after
* this T2BODY callback. */
void
mime_parser_set_t2body (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie, int level))
{
ctx->t2body = fnc;
}
/* Set the callback used to announce a new part. It will be called
* with the media type and media subtype of the part. If no
* Content-type header was given both values are the empty string.
* The callback should return 0 on success or an error code. The
* error code GPG_ERR_FALSE indicates that the caller is not
* interested in the part and data shall not be returned via a
* registered part_data callback. The error code GPG_ERR_TRUE
* indicates that the parts shall be redurned in decoded format
* (i.e. base64 or QP encoding is removed). */
void
mime_parser_set_new_part (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *mediatype,
const char *mediasubtype))
{
ctx->new_part = fnc;
}
/* Set the callback used to return the data of a part to the caller.
* The end of the part is indicated by passing NUL for DATA. */
void
mime_parser_set_part_data (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const void *data,
size_t datalen))
{
ctx->part_data = fnc;
}
/* Set the callback to collect encrypted data. A NULL passed to the
* callback indicates the end of the encrypted data; the callback may
* then decrypt the collected data. */
void
mime_parser_set_collect_encrypted (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_encrypted = fnc;
}
/* Set the callback to collect signed data. A NULL passed to the
* callback indicates the end of the signed data. */
void
mime_parser_set_collect_signeddata (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_signeddata = fnc;
}
/* Set the callback to collect the signature. A NULL passed to the
* callback indicates the end of the signature; the callback may the
* verify the signature. */
void
mime_parser_set_collect_signature (mime_parser_t ctx,
gpg_error_t (*fnc) (void *cookie,
const char *data))
{
ctx->collect_signature = fnc;
}
/* Return the RFC888 parser context. This is only available inside a
* callback. */
rfc822parse_t
mime_parser_rfc822parser (mime_parser_t ctx)
{
return ctx->msg;
}
/* Helper for mime_parser_parse. */
static gpg_error_t
process_part_data (mime_parser_t ctx, char *line, size_t *length)
{
gpg_error_t err;
size_t nbytes;
if (!ctx->want_part)
return 0;
if (!ctx->part_data)
return 0;
if (ctx->decode_part == 1)
{
*length = qp_decode (line, *length, NULL);
}
else if (ctx->decode_part == 2)
{
log_assert (ctx->b64state);
err = b64dec_proc (ctx->b64state, line, *length, &nbytes);
if (err)
return err;
*length = nbytes;
}
return ctx->part_data (ctx->cookie, line, *length);
}
/* Read and parse a message from FP and call the appropriate
* callbacks. */
gpg_error_t
mime_parser_parse (mime_parser_t ctx, estream_t fp)
{
gpg_error_t err;
rfc822parse_t msg = NULL;
unsigned int lineno = 0;
size_t length;
char *line;
line = ctx->line;
msg = rfc822parse_open (parse_message_cb, ctx);
if (!msg)
{
err = gpg_error_from_syserror ();
log_error ("can't open mail parser: %s", gpg_strerror (err));
goto leave;
}
/* Fixme: We should not use fgets because it can't cope with
embedded nul characters. */
while (es_fgets (ctx->line, sizeof (ctx->line), fp))
{
lineno++;
if (lineno == 1 && !strncmp (line, "From ", 5))
continue; /* We better ignore a leading From line. */
length = strlen (line);
if (length && line[length - 1] == '\n')
line[--length] = 0;
else
log_error ("mail parser detected too long or"
" non terminated last line (lnr=%u)\n", lineno);
if (length && line[length - 1] == '\r')
line[--length] = 0;
ctx->err = 0;
if (rfc822parse_insert (msg, line, length))
{
err = gpg_error_from_syserror ();
log_error ("mail parser failed: %s", gpg_strerror (err));
goto leave;
}
if (ctx->err)
{
/* Error from a callback detected. */
err = ctx->err;
goto leave;
}
/* Debug output. Note that the boundary is shown before n_skip
* is evaluated. */
if (ctx->show.boundary)
{
if (ctx->debug)
log_debug ("# Boundary: %s\n", line);
ctx->show.boundary = 0;
}
if (ctx->show.n_skip)
ctx->show.n_skip--;
else if (ctx->show.data)
{
if (ctx->show.as_note)
{
if (ctx->verbose)
log_debug ("# Note: %s\n", line);
ctx->show.as_note = 0;
}
else if (ctx->debug)
log_debug ("# Data: %s\n", line);
}
else if (ctx->show.header && ctx->verbose)
log_debug ("# Header: %s\n", line);
if (ctx->pgpmime == PGPMIME_IN_ENCVERSION)
{
trim_trailing_spaces (line);
if (!*line)
; /* Skip empty lines. */
else if (!strcmp (line, "Version: 1"))
ctx->pgpmime = PGPMIME_WAIT_ENCDATA;
else
{
log_error ("invalid PGP/MIME structure;"
" garbage in pgp-encrypted part ('%s')\n", line);
ctx->pgpmime = PGPMIME_INVALID;
}
}
else if (ctx->pgpmime == PGPMIME_IN_ENCDATA)
{
if (ctx->collect_encrypted)
{
err = ctx->collect_encrypted (ctx->cookie, line);
if (!err)
err = ctx->collect_encrypted (ctx->cookie, "\r\n");
if (err)
goto leave;
}
}
else if (ctx->pgpmime == PGPMIME_GOT_ENCDATA)
{
ctx->pgpmime = PGPMIME_NONE;
if (ctx->collect_encrypted)
ctx->collect_encrypted (ctx->cookie, NULL);
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA)
{
/* If we are processing signed data, store the signed data.
* We need to delay the hashing of the CR/LF because the
* last line ending belongs to the next boundary. This is
* the reason why we can't use the PGPMIME state as a
* condition. */
if (ctx->debug)
log_debug ("# hashing %s'%s'\n",
ctx->delay_hashing? "CR,LF+":"", line);
if (ctx->collect_signeddata)
{
if (ctx->delay_hashing)
ctx->collect_signeddata (ctx->cookie, "\r\n");
ctx->collect_signeddata (ctx->cookie, line);
}
ctx->delay_hashing = 1;
err = process_part_data (ctx, line, &length);
if (err)
goto leave;
}
else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE)
{
if (ctx->collect_signeddata)
{
ctx->collect_signature (ctx->cookie, line);
ctx->collect_signature (ctx->cookie, "\r\n");
}
}
else if (ctx->pgpmime == PGPMIME_GOT_SIGNATURE)
{
ctx->pgpmime = PGPMIME_NONE;
if (ctx->collect_signeddata)
ctx->collect_signature (ctx->cookie, NULL);
}
else
{
err = process_part_data (ctx, line, &length);
if (err)
goto leave;
}
}
rfc822parse_close (msg);
msg = NULL;
err = 0;
leave:
rfc822parse_cancel (msg);
return err;
}
diff --git a/tools/watchgnupg.c b/tools/watchgnupg.c
index 38ee11bd4..8cdc257f8 100644
--- a/tools/watchgnupg.c
+++ b/tools/watchgnupg.c
@@ -1,633 +1,633 @@
/* watchgnupg.c - Socket server for GnuPG logs
* Copyright (C) 2003, 2004, 2010 Free Software Foundation, Inc.
* Copyright (C) 2003, 2004, 2010, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <time.h>
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#define PGM "watchgnupg"
/* Allow for a standalone build on most systems. */
#ifdef VERSION
#define MYVERSION_LINE PGM " ("GNUPG_NAME") " VERSION
#define BUGREPORT_LINE "\nReport bugs to <bug-gnupg@gnu.org>.\n"
#else
#define MYVERSION_LINE PGM " (standalone build) " __DATE__
#define BUGREPORT_LINE ""
#endif
#if !defined(SUN_LEN) || !defined(PF_LOCAL) || !defined(AF_LOCAL)
#define GNUPG_COMMON_NEED_AFLOCAL
#include "../common/mischelp.h"
#endif
#ifndef GNUPG_DEF_COPYRIGHT_LINE
#define GNUPG_DEF_COPYRIGHT_LINE "Copyright (C) 2020 g10 Code GmbH"
#endif
static int verbose;
static int time_only;
static void
die (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
exit (1);
}
static void
err (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", PGM);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
}
static void *
xmalloc (size_t n)
{
void *p = malloc (n);
if (!p)
die ("out of core");
return p;
}
static void *
xcalloc (size_t n, size_t m)
{
void *p = calloc (n, m);
if (!p)
die ("out of core");
return p;
}
static void *
xrealloc (void *old, size_t n)
{
void *p = realloc (old, n);
if (!p)
die ("out of core");
return p;
}
struct client_s {
struct client_s *next;
int fd;
size_t size; /* Allocated size of buffer. */
size_t len; /* Current length of buffer. */
unsigned char *buffer; /* Buffer to with data already read. */
};
typedef struct client_s *client_t;
/* The list of all connected peers. */
static client_t client_list;
static void
print_fd_and_time (int fd)
{
struct tm *tp;
time_t atime = time (NULL);
tp = localtime (&atime);
if (time_only)
printf ("%3d - %02d:%02d:%02d ",
fd,
tp->tm_hour, tp->tm_min, tp->tm_sec );
else
printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ",
fd,
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec );
}
/* Print LINE for the client identified by C. Calling this function
with LINE set to NULL, will flush the internal buffer. */
static void
print_line (client_t c, const char *line)
{
const char *s;
size_t n;
if (!line)
{
if (c->buffer && c->len)
{
print_fd_and_time (c->fd);
fwrite (c->buffer, c->len, 1, stdout);
putc ('\n', stdout);
c->len = 0;
}
return;
}
while ((s = strchr (line, '\n')))
{
print_fd_and_time (c->fd);
if (c->buffer && c->len)
{
fwrite (c->buffer, c->len, 1, stdout);
c->len = 0;
}
fwrite (line, s - line + 1, 1, stdout);
line = s + 1;
}
n = strlen (line);
if (n)
{
if (c->len + n >= c->size)
{
c->size += ((n + 255) & ~255);
c->buffer = (c->buffer
? xrealloc (c->buffer, c->size)
: xmalloc (c->size));
}
memcpy (c->buffer + c->len, line, n);
c->len += n;
}
}
static void
setup_client (int server_fd, int is_un)
{
struct sockaddr_un addr_un;
struct sockaddr_in addr_in;
struct sockaddr *addr;
socklen_t addrlen;
int fd;
client_t client;
if (is_un)
{
addr = (struct sockaddr *)&addr_un;
addrlen = sizeof addr_un;
}
else
{
addr = (struct sockaddr *)&addr_in;
addrlen = sizeof addr_in;
}
fd = accept (server_fd, addr, &addrlen);
if (fd == -1)
{
printf ("[accepting %s connection failed: %s]\n",
is_un? "local":"tcp", strerror (errno));
}
else if (fd >= FD_SETSIZE)
{
close (fd);
printf ("[connection request denied: too many connections]\n");
}
else
{
for (client = client_list; client && client->fd != -1;
client = client->next)
;
if (!client)
{
client = xcalloc (1, sizeof *client);
client->next = client_list;
client_list = client;
}
client->fd = fd;
printf ("[client at fd %d connected (%s)]\n",
client->fd, is_un? "local":"tcp");
}
}
/* Get the logsocketname by reading from gpgconf. Caller needs to
* release the buffer. */
static char *
get_logname (const char *homedir)
{
int rp[2];
pid_t pid;
int i, c;
unsigned int pos;
char filename[1024], *buf;
FILE *fp;
if (pipe (rp) == -1)
die ("error creating a pipe: %s", strerror (errno));
pid = fork ();
if (pid == -1)
die ("error forking process: %s", strerror (errno));
if (!pid)
{ /* Child. */
int fd;
fd = open ("/dev/null", O_WRONLY);
if (fd == -1)
die ("can't open '/dev/null': %s", strerror (errno));
if (fd != 0 && dup2 (fd, 0) == -1)
die ("dup2 stderr failed: %s", strerror (errno));
/* Connect stdout to our pipe. */
if (rp[1] != 1 && dup2 (rp[1], 1) == -1)
die ("dup2 stdout failed: %s", strerror (errno));
/* Close other files. 20 is an arbitrary number; at this point
* we have not opened many files. */
for (i=3; i < 20; i++)
close (i);
errno = 0;
if (homedir)
execlp ("gpgconf", "gpgconf", "--homedir", homedir,
"-0", "--list-dirs", "socketdir", NULL);
else
execlp ("gpgconf", "gpgconf",
"-0", "--list-dirs", "socketdir", NULL);
die ("failed to exec gpgconf: %s", strerror (errno));
}
/* Parent. */
close (rp[1]);
fp = fdopen (rp[0], "r");
if (!fp)
die ("can't fdopen pipe for reading: %s", strerror (errno));
pos = 0;
while ((c=getc (fp)) != EOF)
{
if (pos+1 >= sizeof filename)
die ("gpgconf returned a too long filename\n");
filename[pos++] = c;
if (!c)
break;
}
fclose (fp);
if (c == EOF)
die ("error reading from gpgconf: %s", "premature EOF");
while ((i=waitpid (pid, NULL, 0)) == -1 && errno == EINTR)
;
if (i == -1)
die ("waiting for gpgconf failed: %s", strerror (errno));
buf = xmalloc (strlen (filename) + 6 + 1);
strcpy (buf, filename);
strcat (buf, "/S.log");
return buf;
}
static void
print_version (int with_help)
{
fputs (MYVERSION_LINE "\n"
GNUPG_DEF_COPYRIGHT_LINE "\n"
"License GPLv3+: "
"GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>\n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n",
stdout);
if (with_help)
fputs
("\n"
"Usage: " PGM " [OPTIONS] [SOCKETNAME]\n"
" " PGM " [OPTIONS] PORT [SOCKETNAME]\n"
"Open the local socket SOCKETNAME (or the TCP port PORT)\n"
"and display log messages. If --tcp is not used and no\n"
"socket name is given, it is taken from the gpgconf tool.\n"
"\n"
" --tcp listen on a TCP port and optionally on a local socket\n"
" --force delete an already existing socket file\n"
" --verbose enable extra informational output\n"
" --time-only print only the time; not a full timestamp\n"
" --homedir DIR use DIR for gpgconf's --homedir option\n"
" --version print version of the program and exit\n"
" --help display this help and exit\n"
BUGREPORT_LINE, stdout );
exit (0);
}
int
main (int argc, char **argv)
{
int last_argc = -1;
int force = 0;
int tcp = 0;
char *homedir = NULL;
char *logname = NULL;
struct sockaddr_un srvr_addr_un;
struct sockaddr_in srvr_addr_in;
struct sockaddr *addr_in = NULL;
struct sockaddr *addr_un = NULL;
socklen_t addrlen_in, addrlen_un;
unsigned short port;
int server_un, server_in;
int flags;
if (argc)
{
argc--; argv++;
}
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
else if (!strcmp (*argv, "--version"))
print_version (0);
else if (!strcmp (*argv, "--help"))
print_version (1);
else if (!strcmp (*argv, "--verbose"))
{
verbose = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--time-only"))
{
time_only = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--force"))
{
force = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--tcp"))
{
tcp = 1;
argc--; argv++;
}
else if (!strcmp (*argv, "--homedir"))
{
argc--; argv++;
if (!argc)
die ("option --homedir requires an argument\n");
argc--;
homedir = *argv++;
}
}
if (!tcp && argc == 1)
;
else if (tcp && (argc == 1 || argc == 2))
; /* Option --tcp optionally allows to also read from a socket. */
else if (!tcp && !argc)
{
/* No args given - figure out the socket using gpgconf. We also
* force overwriting the socket because the constructed name
- * can't be some accidently given name. */
+ * can't be some accidentally given name. */
logname = get_logname (homedir);
force = 1;
}
else
{
fprintf (stderr, "usage: " PGM " [socketname]\n"
" " PGM " --tcp port [socketname]\n");
exit (1);
}
if (tcp)
{
port = atoi (*argv);
argc--; argv++;
}
else
{
port = 0;
}
if (argc)
logname = *argv;
setvbuf (stdout, NULL, _IOLBF, 0);
if (tcp)
{
int i = 1;
server_in = socket (PF_INET, SOCK_STREAM, 0);
if (server_in == -1)
die ("socket(PF_INET) failed: %s\n", strerror (errno));
if (setsockopt (server_in, SOL_SOCKET, SO_REUSEADDR,
(unsigned char *)&i, sizeof (i)))
err ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
if (verbose)
fprintf (stderr, "listening on port %hu\n", port);
}
else
server_in = -1;
if (logname)
{
server_un = socket (PF_LOCAL, SOCK_STREAM, 0);
if (server_un == -1)
die ("socket(PF_LOCAL) failed: %s\n", strerror (errno));
if (verbose)
fprintf (stderr, "listening on socket '%s'\n", logname);
}
else
server_un = -1;
/* We better set the listening socket to non-blocking so that we
don't get bitten by race conditions in accept. The should not
happen for Unix Domain sockets but well, shit happens. */
if (server_in != -1)
{
flags = fcntl (server_in, F_GETFL, 0);
if (flags == -1)
die ("fcntl (F_GETFL) failed: %s\n", strerror (errno));
if ( fcntl (server_in, F_SETFL, (flags | O_NONBLOCK)) == -1)
die ("fcntl (F_SETFL) failed: %s\n", strerror (errno));
}
if (server_un != -1)
{
flags = fcntl (server_un, F_GETFL, 0);
if (flags == -1)
die ("fcntl (F_GETFL) failed: %s\n", strerror (errno));
if ( fcntl (server_un, F_SETFL, (flags | O_NONBLOCK)) == -1)
die ("fcntl (F_SETFL) failed: %s\n", strerror (errno));
}
if (tcp)
{
memset (&srvr_addr_in, 0, sizeof srvr_addr_in);
srvr_addr_in.sin_family = AF_INET;
srvr_addr_in.sin_port = htons (port);
srvr_addr_in.sin_addr.s_addr = htonl (INADDR_ANY);
addr_in = (struct sockaddr *)&srvr_addr_in;
addrlen_in = sizeof srvr_addr_in;
}
if (logname)
{
memset (&srvr_addr_un, 0, sizeof srvr_addr_un);
srvr_addr_un.sun_family = AF_LOCAL;
strncpy (srvr_addr_un.sun_path, logname,
sizeof (srvr_addr_un.sun_path)-1);
srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path) - 1] = 0;
addr_un = (struct sockaddr *)&srvr_addr_un;
addrlen_un = SUN_LEN (&srvr_addr_un);
}
else
addrlen_un = 0; /* Silent gcc. */
if (server_in != -1 && bind (server_in, addr_in, addrlen_in))
die ("bind to port %hu failed: %s\n", port, strerror (errno));
again:
if (server_un != -1 && bind (server_un, addr_un, addrlen_un))
{
if (errno == EADDRINUSE && force)
{
force = 0;
remove (srvr_addr_un.sun_path);
goto again;
}
else
die ("bind to '%s' failed: %s\n", logname, strerror (errno));
}
if (server_in != -1 && listen (server_in, 5))
die ("listen on inet failed: %s\n", strerror (errno));
if (server_un != -1 && listen (server_un, 5))
die ("listen on local failed: %s\n", strerror (errno));
for (;;)
{
fd_set rfds;
int max_fd;
client_t client;
/* Usually we don't have that many connections, thus it is okay
to set them always from scratch and don't maintain an active
fd_set. */
FD_ZERO (&rfds);
max_fd = -1;
if (server_in != -1)
{
FD_SET (server_in, &rfds);
max_fd = server_in;
}
if (server_un != -1)
{
FD_SET (server_un, &rfds);
if (server_un > max_fd)
max_fd = server_un;
}
for (client = client_list; client; client = client->next)
if (client->fd != -1)
{
FD_SET (client->fd, &rfds);
if (client->fd > max_fd)
max_fd = client->fd;
}
if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* Ignore any errors. */
if (server_in != -1 && FD_ISSET (server_in, &rfds))
setup_client (server_in, 0);
if (server_un != -1 && FD_ISSET (server_un, &rfds))
setup_client (server_un, 1);
for (client = client_list; client; client = client->next)
if (client->fd != -1 && FD_ISSET (client->fd, &rfds))
{
char line[256];
int n;
n = read (client->fd, line, sizeof line - 1);
if (n < 0)
{
int save_errno = errno;
print_line (client, NULL); /* flush */
printf ("[client at fd %d read error: %s]\n",
client->fd, strerror (save_errno));
close (client->fd);
client->fd = -1;
}
else if (!n)
{
print_line (client, NULL); /* flush */
close (client->fd);
printf ("[client at fd %d disconnected]\n", client->fd);
client->fd = -1;
}
else
{
line[n] = 0;
print_line (client, line);
}
}
}
return 0;
}
/*
Local Variables:
compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c"
End:
*/

File Metadata

Mime Type
application/octet-stream
Expires
Mon, Apr 22, 10:40 AM (1 d, 23 h)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
JUHq2ewGX5yI

Event Timeline