diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 7182d90d9..1313cbe04 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,6 @@ # indent: Modernize mem2str. 6a80d6f9206eae2c867c45daa5cd3e7d6c6ad114 +# doc: Fix spelling errors found by lintian. +2ed1f68b48db7b5503045386de0500fddf70077e +# indent: Re-indent a function +869d1df270c0ccc3a9f792167b96d678a932b37e diff --git a/AUTHORS b/AUTHORS index bd1d528e3..04b0ce701 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,7 +16,7 @@ List of Copyright holders ========================= Copyright (C) 1997-2019 Werner Koch - Copyright (C) 2003-2023 g10 Code GmbH + Copyright (C) 2003-2025 g10 Code GmbH Copyright (C) 1994-2021 Free Software Foundation, Inc. Copyright (C) 2002 Klarälvdalens Datakonsult AB Copyright (C) 1995-1997, 2000-2007 Ulrich Drepper @@ -184,6 +184,9 @@ Ben McGinnes Christian Aistleitner 2013-05-26:20130626112332.GA2228@quelltextlich.at: +Collin Funk +2025-04-29:87y0vi5imt.fsf@gmail.com: + Damien Goutte-Gattat 2015-01-17:54BA49AA.2040708@incenp.org: @@ -220,6 +223,9 @@ Jussi Kivilinna Kyle Butt 2013-05-29:CAAODAYLbCtqOG6msLLL0UTdASKWT6u2ptxsgUQ1JpusBESBoNQ@mail.gmail.com: +Mario Haustein +2022-09-26:8149069.T7Z3S40VBb@localdomain: + Michael Haubenwallner 2018-07-13:c397e637-f1ce-34f0-7e6a-df04a76e1c35@ssi-schaefer.com: @@ -230,6 +236,9 @@ Phil Pennock Rainer Perske 2017-10-24:permail-2017102014511105be2aed00002fc6-perske@message-id.uni-muenster.de: +Ramón García Fernández +2015-02-09:CA+=ghChvG7GqRhb25eTSxXT16z-qah2tWwCaxp5xQ4PZt2TF1w@mail.gmail.com: + Stefan Tomanek 2014-01-30:20140129234449.GY30808@zirkel.wertarbyte.de: diff --git a/Makefile.am b/Makefile.am index 0f2075089..6bfe0ece0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,13 +18,13 @@ ## Process this file with automake to produce Makefile.in -# To include the wixlibs for building an MSI installer in a release use -# make release WITH_MSI=1 +# We want to also build the wixlib for use by GnuPG Desktop +WITH_MSI=1 # Location of the released tarball archives. This is prefixed by # the variable RELEASE_ARCHIVE in ~/.gnupg-autogen.rc. For example: # RELEASE_ARCHIVE=user@host:archive/tarballs -RELEASE_ARCHIVE_SUFFIX = gnupg/v2.4 +RELEASE_ARCHIVE_SUFFIX = gnupg/v2.5 # The variable RELEASE_SIGNKEY in ~/.gnupg-autogen.rc is used # to specify the key for signing. For example: # RELEASE_SIGNKEY=D8692123C4065DEA5E0F3AB5249B39D24F25E3B6 @@ -163,7 +163,7 @@ dist-hook: gen-ChangeLog distcheck-hook: set -e; ( \ - pref="#+macro: gnupg24_" ;\ + pref="#+macro: gnupg26_" ;\ reldate="$$(date -u +%Y-%m-%d)" ;\ echo "$${pref}ver $(PACKAGE_VERSION)" ;\ echo "$${pref}date $${reldate}" ;\ @@ -191,7 +191,7 @@ endif gen_start_date = 2011-12-01T06:00:00 -.PHONY: gen-ChangeLog +.PHONY: gen-ChangeLog stowinstall speedo gen-ChangeLog: if test -e $(top_srcdir)/.git; then \ (cd $(top_srcdir) && \ @@ -207,6 +207,11 @@ gen-ChangeLog: stowinstall: $(MAKE) $(AM_MAKEFLAGS) install prefix=/usr/local/stow/gnupg + +speedo: + $(MAKE) -f $(top_srcdir)/build-aux/speedo.mk native SELFCHECK=0 + + TESTS_ENVIRONMENT = \ LC_ALL=C \ EXEEXT=$(EXEEXT) \ @@ -242,8 +247,8 @@ release: mkopt=""; \ if [ -n "$$CUSTOM_SWDB" ]; then \ mkopt="CUSTOM_SWB=1"; \ - x=$$(grep '^OVERRIDE_TARBALLS=' \ - $$HOME/.gnupg-autogen.rc|cut -d= -f2);\ + x=$$(grep '^[[:blank:]]*OVERRIDE_TARBALLS[[:blank:]]*=' \ + $$HOME/.gnupg-autogen.rc|cut -d= -f2|xargs);\ if [ -f "$$x/swdb.lst" ]; then \ echo "/* Copying swdb.lst from the overrides directory */"; \ cp "$$x/swdb.lst" . ; \ @@ -270,13 +275,15 @@ release: sign-release: +(set -e; \ test $$(pwd | sed 's,.*/,,') = dist || cd dist; \ - x=$$(grep '^RELEASE_ARCHIVE=' $$HOME/.gnupg-autogen.rc|cut -d= -f2);\ + x=$$(grep '^[[:blank:]]*RELEASE_ARCHIVE[[:blank:]]*=' \ + $$HOME/.gnupg-autogen.rc|cut -d= -f2|xargs);\ if [ -z "$$x" ]; then \ echo "error: RELEASE_ARCHIVE missing in ~/.gnupg-autogen.rc">&2; \ exit 2;\ fi;\ myarchive="$$x/$(RELEASE_ARCHIVE_SUFFIX)";\ - x=$$(grep '^RELEASE_SIGNKEY=' $$HOME/.gnupg-autogen.rc|cut -d= -f2);\ + x=$$(grep '^[[:blank:]]*RELEASE_SIGNKEY[[:blank:]]*=' \ + $$HOME/.gnupg-autogen.rc|cut -d= -f2|xargs);\ if [ -z "$$x" ]; then \ echo "error: RELEASE_SIGNKEY missing in ~/.gnupg-autogen.rc">&2; \ exit 2;\ @@ -305,8 +312,9 @@ sign-release: gpg -sbu $$mysignkey $${wixlibfile} ;\ fi; \ cat $(RELEASE_NAME).swdb >swdb.snippet;\ - echo '#+macro: gnupg24_branch STABLE-BRANCH-2-4' >>swdb.snippet;\ + echo '#+macro: gnupg26_branch master' >>swdb.snippet;\ cat $${release_w32_name}.exe.swdb >>swdb.snippet;\ + echo "#+macro: gnupg26_w32_ssiz $$(wc -c <$${release_w32_name}.tar.xz|awk '{print int($$1/1024)}')k" >>swdb.snippet ;\ echo >>swdb.snippet ;\ sha1sum $${files1} >>swdb.snippet ;\ cat "../$(RELEASE_NAME).buildlog" swdb.snippet \ diff --git a/NEWS b/NEWS index d2f0da2ac..a43fe024f 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,637 @@ -Noteworthy changes in version 2.5.0 (unreleased) +Noteworthy changes in version 2.5.9 (unreleased) ------------------------------------------------ + * gpg: Add the revocation reason to the sigclass of a "rev" line. + Regression in 2.5.7. [T7073] + + * gpg: Do not show the non-standard secp256k1 curve in the menu to + select the curve. It can however be specified using its name. + + + Release-info: https://dev.gnupg.org/T7695 + + +Noteworthy changes in version 2.5.8 (2025-06-20) +------------------------------------------------ + + * gpg: Show revocation reason with a standard -k listing. [T7083] + + * gpg: Emit a revocation reason as comment in a "pub" record. + [T7083] + + * agent: Fix regression in 2.5.7 decrypting with a card based + cv25519 key. [T7676] + + * scd:openpgp: Fix a regression in exporting card based ed25519 ssh + keys. [T7589] + + * dirmngr: Do not require a keyserver for "gpg --fetch-key". + [T7693] + + + See-also: gnupg-announce/2025q2/000494.html + Release-info: https://dev.gnupg.org/T7672 + + +Noteworthy changes in version 2.5.7 (2025-06-02) +------------------------------------------------ + + * gpg: Allow updating a SHA-1 key certification w/o using + the --force-sign-key option. [T7663] + + * gpg: The group key flag has now been fully implemented. + [rG8833a34bf0] + + * gpg: Make combination of show-only-fpr-mbox and show-unusable-uid + work. [rGd5a4a2dc89] + + * gpg: Do not allow compressed key packets on import. [T7014] + + * gpgsm: Allow an empty subject DN also during import. [T7171] + + * agent: Recover the old behavior with max-cache-ttl=0. [T6681] + + * agent: Fix ECC key on smartcard for composite KEM with PQC. + [T7648] + + * scd: Fix a harmless read buffer over-read in a function used by + PKCS#15 cards. [T7662] + + * gpg-mail-tube,wks: Support templates for mail content. [T7381] + + * Use the KEM interface of Libgcrypt for encryption/decryption. + [T7649] + + * Fix a glitch in socket handling in Windows in case of a nonce + mismatch. [rG645cf7d8fc] + + + See-also: gnupg-announce/2025q2/000493.html + Release-info: https://dev.gnupg.org/T7671 + + +Noteworthy changes in version 2.5.6 (2025-05-08) +------------------------------------------------ + + * gpg: Add a flag to the filter expressions for left anchored + substring match. [rGc12b7d047e] + + * gpg: New list option "show-trustsig" to avoid resorting to colon + mode for this info. [rG41d6ae8f41] + + * gpg: New command --quick-tsign-key to create a trust signature. + [rGd90b290f97] + + * gpg: New keygen parameter "User-Id". [rGcfd597c603] + + * gpg: New list options "show-trustsig". [rGrG41d6ae8f41] + + * gpg: Fix double free of internal data in no-sig-cache mode [T7547] + + * gpg: Signatures from revoked or expired keys do not anymore show + up as missing keys. Fixes regression in 2.5.5. [T7583] + + * gpgsm: Extend --learn-card by an optional s/n argument. [T7379] + + * gpgsm: Skip expired certificates when selection a certificate by + subject. [rG4cf83273e8] + + * card: New command "ll" as alias for "list --cards". [rGd6ee7adebe] + + * scd: Fix posssible lockup on Windows due to a lost select + result. [rGa7ec3792c5] + + * scd:p15: Accept P15 cards with a zero-length label. [rGdb25aa9887] + + * keyboxd: Use case-insensitive search for mail addresses. [T7576] + + * dirmngr: Fix a problem in libdns related to an address change from + 127.0.0.1. [T4021] + + * gpgconf: Fix reload and kill of keyboxd. [T7569] + + * Fix logic for certain recsel conditions. [rG8968e84903] + + * Add Solaris support to get_signal_name. [T7638] + + * Fix build error of the test shell on AIX. [T7632] + + See-also: gnupg-announce/2025q2/000492.html + Release-info: https://dev.gnupg.org/T7586 + + +Noteworthy changes in version 2.5.5 (2025-03-07) +------------------------------------------------ + + * gpg: Fix a verification DoS due to a malicious subkey in the + keyring. [T7527] + + * dirmngr: Fix possible hangs due to blocking connection requests. + [T6606, T7434] + + * w32: On socket nonce mismatch close the socket. [T7434] + + * w32: Print more detailed diagnostics for IPC errors. + + * GPGME is not any more distributed with the Windows installer. + Please install gpg4win to get gpgme version. + + See-also: gnupg-announce/2025q1/000491.html + Release-info: https://dev.gnupg.org/T7530 + + +Noteworthy changes in version 2.5.4 (2025-02-12) +------------------------------------------------ + + * gpg: New option --disable-pqc-encryption. [rG00c31f8b04] + + * gpg: Fix --quick-add-key for Weierstrass ECC with usage given. + [T7506] + + * gpg: Fix handling with no CRC armor. [T7071] + + * gpg: New private Kyber keys are now cross-referenced using a new + Link attribute. [T6638] + + * gpg: Fix an import problem with keys having another primary key as + a subkey. [T7527] + + * gpgsm: Allow unattended PKCS#12 export without passphrase. + [rG159e801043] + + * gpgsm: Allow CSR generation with an unprotected key. + [rG89055f24f4] + + * agent: New option --change-std-env-name. [T7522] + + * agent: Fix ssh-agent's request_identities for skipped Brainpool + keys. [rG2469dc5aae] + + * Do not package zlib and bzip2 object files in a speedo release + build. [T7442] + + See-also: gnupg-announce/2025q1/000490.html + Release-info: https://dev.gnupg.org/T7480 + + +Noteworthy changes in version 2.5.3 (2025-01-09) +------------------------------------------------ + + * gpg: Allow for signature subpackets of up to 30000 octets. + [rG36dbca3e69] + + * gpg: Silence expired trusted-key diagnostics in quiet mode. [T7351] + + * gpg: Allow smaller session keys with Kyber and enforce the use of + AES-256 if useful. [T7472] + + * gpg: Fix regression in key generation from existing card key. + [T7309,T7457] + + * gpg: Print a warning if the card backup key could not be written. + [T2169] + + * The --supervised options of gpg-agent and dirmngr have been + renamed to --deprecated-supervised as preparation for their + removal. [rGa019a0fcd8] + + * There is no more default for a keyserver. + + See-also: gnupg-announce/2025q1/000489.html + Release-info: https://dev.gnupg.org/T7442 + + +Noteworthy changes in version 2.5.2 (2024-12-05) +------------------------------------------------ + + * gpg: Add option 16 to --full-gen-key to create ECC+Kyber. [T6638] + + * gpg: For composite algos add the algo string to the colons + listings. [T6638] + + * gpg: Validate the trustdb after the import of a trusted key. + [T7200] + + * gpg: Exclude expired trusted keys from the key validation process. + [T7200] + + * gpg: Fix a wrong decryption failed status for signed and OCB + encrypted messages without a signature verification key. [T7042] + + * gpg: Retain binary representation for import->export with Ed25519 + key signatures. [T7426] + + * gpg: Fix comparing ed448 to ed25519 with --assert-pubkey-algo. + [T7425] + + * gpg: Avoid a failure exit code for expired ultimately trusted + keys. [T7351] + + * gpg: Emit status error for an invalid ADSK. [T7322] + + * gpg: Allow the use of an ADSK subkey as ADSK subkey. [T6882] + + * gpg: Fix --quick-set-expire for V5 subkey fingerprints. [T7298] + + * gpg: Robust error handling for SCD READKEY. [T7309] + + * gpg: Fix cv25519 v5 export regression. [T7316] + + * gpgsm: Nearly fourfold speedup of validated certificate listings. + [T7308] + + * gpgsm: Improvement for some rare P12 files. [rGf50dde6269] + + * gpgsm: Terminate key listing on output write error. [T6185] + + * agent: Add option --status to the LISTRUSTED command. + [rG4275d5fa7a] + + * agent: Fix detection of the yet unused trustflag de-vs. [T5079] + + * agent: Allow ssh to sign data larger than the Assuan line length. + [T7436] + + * keyboxd: Fix a race condition on the database handle. [T7294] + + * dirmngr: A list of used URLs for loaded CRLs is printed first in + the output of the LISTCRL command. [T7337] + + * scd: More mitigations against lock ups with multiple cards or + apps. [T7323, T7402] + + * gpgtar: Use log-file from common.conf only in --batch mode. + [rGb389e04ef5] + + * gpgtar: Fix directory creation during extraction. [T7380] + + * gpg-mail-tube: Minor fixes. + + * gpgconf: Add list flag to trusted-key et al. [T7313] + + * Implement GNUPG_ASSUME_COMPLIANCE envvar and registry key for + testing de-vs compliance mode. [rGb287fb5775,rG7b0be541a9] + + * Enable additional runtime protections in speedo builds for + Windows. [rG39aa206dc5] + + * Fix a race condition in creating the socket directory. [T7332] + + * Fix a build problem on macOS (missing unistd.h). [T7193] + + + See-also: gnupg-announce/2024q4/000488.html + Release-info: https://dev.gnupg.org/T7289 + + +Noteworthy changes in version 2.5.1 (2024-09-12) +------------------------------------------------ + + * gpg: The support for composite Kyber+ECC public key algorithms + does now use the final FIPS-203 and LibrePGP specifications. The + experimental keys from 2.5.0 are no longer supported. [T6815] + + * gpg: New commands --add-recipients and --change-recipients. + [T1825] + + * gpg: New option --proc-all-sigs. [T7261] + + * gpg: Fix a regression in 2.5.0 in gpgme's tests. [T7195] + + * gpg: Make --no-literal work again for -c and --store. [T5852] + + * gpg: Improve detection of input data read errors. [T6528] + + * gpg: Fix getting key by IPGP record (rfc-4398). [T7288] + + * gpgsm: New option --assert-signer. [T7286] + + * gpgsm: More improvements to PKCS#12 parsing to cope with latest + IVBB changes. [T7213] + + * agent: Fix KEYTOCARD command when used with a loopback pinentry. + [T7283] + + * gpg-mail-tube: Make sure GNUPGHOME is set in vsd mode. New option + --as-attach. [rG4511997e9e1b] + + * Now uses the process spawn API from libgpg-error. [T7192,T7194] + + * Removed the --enable-gpg-is-gpg2 configure time option. + [rG2125f228d36c] + + * Die Windows version will now be build for 64-Bit Windows and with + the corresponding changes to the installation directory and + Registry keys. + + + See-also: gnupg-announce/2024q3/000485.html + Release-info: https://dev.gnupg.org/T7191 + + +Noteworthy changes in version 2.5.0 (2024-07-05) +------------------------------------------------ + + * gpg: Support composite Kyber+ECC public key algorithms. This is + experimental due to the yet outstanding FIPS-203 specification. + [T6815] + + * gpg: Allow algo string "pqc" for --quick-gen-key. [rG12ac129a70] + + * gpg: New option --show-only-session-key. [rG1695cf267e] + + * gpg: Print designated revokers also in non-colon listing mode. + [rG9d618d1273] + + * gpg: Make --with-sig-check work with --show-key in non-colon + listing mode. [rG0c34edc443] + + * tpm: Rework error handling and fix key import [T7129, T7186] + + * Varous fixes to improve robustness on 64 bit Windows. [T7139] + + + Changes also found in 2.4.6: + + * gpg: New command --quick-set-ownertrust. [rG967678d972] + + * gpg: Indicate disabled keys in key listings and add list option + "show-ownertrust". [rG2a0a706eb2] + + * gpg: Make sure a DECRYPTION_OKAY is never issued for a bad OCB + tag. [T7042] + + * gpg: Do not allow to accidently set the RENC usage. [T7072] + + * gpg: Accept armored files without CRC24 checksum. [T7071] + + * gpg: New --import-option "only-pubkeys". [T7146] + + * gpg: Repurpose the AKL mechanism "ldap" to work like the keyserver + mechnism but only for LDAP keyservers. [rG068ebb6f1e] + + * gpg: ADSKs are now configurable for new keys. [T6882] + + * gpgsm: Emit user IDs with an empty Subject also in colon mode. + [T7171] + + * agent: Consider an empty pattern file as valid. [rGc27534de95] + + * agent: Fix error handling of READKEY. [T6012] + + * agent: Avoid random errors when storing key in ephemeral mode. + [T7129, rGfdc5003956] + + * agent: Make "SCD DEVINFO --watch" more robust. [T7151] + + * scd: Improve KDF data object handling for OpenPGP cards. [T7058] + + * scd: Avoid buffer overrun with more than 16 PC/SC readers. + [T7129, rG4c1b007035] + + * scd: Fix how the scdaemon on its pipe connection finishes. + [T7160] + + * gpgconf: Check readability of some files with -X and change its + output format. [rG98e287ba6d] + + * gpg-mail-tube: New tool to apply PGP/MIME encryption to a mail. + [rG28a080bc9f] + + * Fix some uninitialized variables and double frees in error code + paths. [T7129] + + + Changes also found in 2.4.5: + + * gpg,gpgv: New option --assert-pubkey-algo. [T6946] + + * gpg: Emit status lines for errors in the compression layer. + [T6977] + + * gpg: Fix invocation with --trusted-keys and --no-options. [T7025] + + * gpgsm: Allow for a longer salt in PKCS#12 files. [T6757] + + * gpgtar: Make --status-fd=2 work on Windows. [T6961] + + * scd: Support for the ACR-122U NFC reader. [rG1682ca9f01] + + * scd: Suport D-TRUST ECC cards. [T7000,T7001] + + * scd: Allow auto detaching of kernel drivers; can be disabled with + the new compatibility-flag ccid-no-auto-detach. [rGa1ea3b13e0] + + * scd: Allow setting a PIN length of 6 also with a reset code for + openpgp cards. [T6843] + + * agent: Allow GET_PASSPHRASE in restricted mode. [rGadf4db6e20] + + * dirmngr: Trust system's root CAs for checking CRL issuers. + [T6963] + + * dirmngr: Fix regression in 2.4.4 in fetching keys via hkps. + [T6997] + + * gpg-wks-client: Make option --mirror work properly w/o specifying + domains. [rG37cc255e49] + + * g13,gpg-wks-client: Allow command style options as in "g13 mount + foo". [rGa09157ccb2] + + * Allow tilde expansion for the foo-program options. [T7017] + + * Make the getswdb.sh tool usable outside the GnuPG tree. + + + Changes also found in 2.4.4: + + * gpg: Do not keep an unprotected smartcard backup key on disk. See + https://gnupg.org/blog/20240125-smartcard-backup-key.html for a + security advisory. [T6944] + + * gpg: Allow to specify seconds since Epoch beyond 2038 on 32-bit + platforms. [T6736] + + * gpg: Fix expiration time when Creation-Date is specified. [T5252] + + * gpg: Add support for Subkey-Expire-Date. [rG96b69c1866] + + * gpg: Add option --with-v5-fingerprint. [T6705] + + * gpg: Add sub-option ignore-attributes to --import-options. + [rGd4976e35d2] + + * gpg: Add --list-filter properties sig_expires/sig_expires_d. + [rGbf662d0f93af] + + * gpg: Fix validity of re-imported keys. [T6399] + + * gpg: Report BEGIN_ status before examining the input. [T6481] + + * gpg: Don't try to compress a read-only keybox. [T6811] + + * gpg: Choose key from inserted card over a non-inserted + card. [T6831] + + * gpg: Allow to create revocations even with non-compliant algos. + [T6929] + + * gpg: Fix regression in the Revoker keyword of the parameter file. + [T6923] + + * gpg: Improve error message for expired default keys. [T4704] + + * gpgsm: Add --always-trust feature. [T6559] + + * gpgsm: Support ECC certificates in de-vs mode. [T6802] + + * gpgsm: Major rewrite of the PKCS#12 parser. [T6536] + + * gpgsm: No not show the pkcs#12 passphrase in debug output. [T6654] + + * keyboxd: Timeout on failure to get the database lock. [T6838] + + * agent: Update the key stubs only if really modified. [T6829] + + * scd: Add support for certain Starcos 3.2 cards. [rG5304c9b080] + + * scd: Add support for CardOS 5.4 cards. [rG812f988059] + + * scd: Add support for D-Trust 4.1/4.4 cards. [rG0b85a9ac09] + + * scd: Add support for Smartcafe Expert 7.0 cards. [T6919] + + * scd: Add a length check for a new PIN. [T6843] + + * tpm: Fix keytotpm handling in the agent. [rG9909f622f6] + + * tpm: Fixes for the TPM test suite. [T6052] + + * dirmngr: Avoid starting a second instance on Windows via GPGME + based launching. [T6833] + + * dirmngr: New option --ignore-crl-extensions. [T6545] + + * dirmngr: Support config value "none" to disable the default + keyserver. [T6708] + + * dirmngr: Implement automatic proxy detection on Windows. [T5768] + + * dirmngr: Fix handling of the HTTP Content-Length. [rGa5e33618f4] + + * dirmngr: Add code to support proxy authentication using the + Negotiation method on Windows. [T6719] + + * gpgconf: Add commands --lock and --unlock. [rG93b5ba38dc] + + * gpgconf: Add keyword socketdir to gpgconf.ctl. [rG239c1fdc28] + + * gpgconf: Adjust the -X command for the new VERSION file format. + [T6918] + + * wkd: Use export-clean for gpg-wks-client's --mirror and --create + commands. [rG2c7f7a5a278c] + + * wkd: Make --add-revocs the default in gpg-wks-client. New option + --no-add-revocs. [rG10c937ee68] + + * Remove duplicated backslashes when setting the homedir. [T6833] + + * Ignore attempts to remove the /dev/null device. [T6556] + + * Improve advisory file lock retry strategy. [T3380] + + * Improve the speedo build system for Unix. [T6710] + + + Changes also found in 2.4.3: + + * gpg: Set default expiration date to 3 years. [T2701] + + * gpg: Add --list-filter properties "key_expires" and + "key_expires_d". [T6529] + + * gpg: Emit status line and proper diagnostics for write errors. + [T6528] + + * gpg: Make progress work for large files on Windows. [T6534] + + * gpg: New option --no-compress as alias for -z0. + + * gpg: Show better error messages for blocked PINs. [T6425] + + * gpgsm: Print PROGRESS status lines. Add new --input-size-hint. + [T6534] + + * gpgsm: Support SENDCERT_SKI for --call-dirmngr. [rG701a8b30f0] + + * gpgsm: Major rewrite of the PKCS#12 parser. [T6536] + + * gpgtar: New option --no-compress. + + * dirmngr: Extend the AD_QUERY command. [rG207c99567c] + + * dirmngr: Disable the HTTP redirect rewriting. [T6477] + + * dirmngr: New option --compatibility-flags. [rGbf04b07327] + + * dirmngr: New option --ignore-crl-extensions. [T6545] + + * dirmngr: Support config value "none" to disable the default + keyserver. [T6708] + + * wkd: Use export-clean for gpg-wks-client's --mirror and --create + commands. [rG2c7f7a5a27] + + * wkd: Make --add-revocs the default in gpg-wks-client. New option + --no-add-revocs. [rG10c937ee68] + + * scd: Make signing work for Nexus cards. [rGb83d86b988] + + * scd: Fix authentication with Administration Key for PIV. + [rG25b59cf6ce] + + * Fix garbled time output in non-English Windows. [T6741] + + + Changes also found in 2.4.2: + + * gpg: Print a warning if no more encryption subkeys are left over + after changing the expiration date. [rGef2c3d50fa] + + * gpg: Fix searching for the ADSK key when adding an ADSK. [T6504] + + * gpgsm: Speed up key listings on Windows. [rG08ff55bd44] + + * gpgsm: Reduce the number of "failed to open policy file" + diagnostics. [rG68613a6a9d] + + * agent: Make updating of private key files more robust and track + display S/N. [T6135] + + * keyboxd: Avoid longish delays on Windows when listing keys. + [rG6944aefa3c] + + * gpgtar: Emit extra status lines to help GPGME. [T6497] + + * w32: Avoid using the VirtualStore. [T6403] + + + Release-info: https://dev.gnupg.org/T7189 + + +Release dates of 2.4 versions +----------------------------- + +Version 2.4.6 (unreleased) https://dev.gnupg.org/T7030 +Version 2.4.5 (2024-03-07) https://dev.gnupg.org/T6960 +Version 2.4.4 (2024-01-25) https://dev.gnupg.org/T6578 +Version 2.4.3 (2023-07-04) https://dev.gnupg.org/T6509 +Version 2.4.2 (2023-05-30) https://dev.gnupg.org/T6506 +Version 2.4.1 (2023-04-28) https://dev.gnupg.org/T6454 +Version 2.4.0 (2022-12-16) https://dev.gnupg.org/T6302 + Noteworthy changes in version 2.4.1 (2023-04-28) ------------------------------------------------ @@ -1209,7 +1840,7 @@ Noteworthy changes in version 2.3.0 (2021-04-07) 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 + gpg-wks-client. This allows one 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 @@ -1253,7 +1884,7 @@ Noteworthy changes in version 2.3.0 (2021-04-07) query. * gpg: Do not store the TOFU trust model in the trustdb. This - allows to enable or disable a TOFO model without triggering a + allows one to enable or disable a TOFO model without triggering a trustdb rebuild. [#4134] * scd: Fix cases of "Bad PIN" after using "forcesig". [#4177] @@ -1595,6 +2226,8 @@ Noteworthy changes in version 2.3.0 (2021-04-07) Release dates of 2.2 versions ----------------------------- +Version 2.2.42 (2023-11-28) https://dev.gnupg.org/T6307 +Version 2.2.41 (2022-12-09) https://dev.gnupg.org/T6280 Version 2.2.40 (2022-10-10) https://dev.gnupg.org/T6181 Version 2.2.39 (2022-09-02) https://dev.gnupg.org/T6175 Version 2.2.38 (2022-09-01) https://dev.gnupg.org/T6159 @@ -1670,7 +2303,7 @@ Noteworthy changes in version 2.1.23 (2017-08-09) to your gpg.conf. * agent: Option --no-grab is now the default. The new option --grab - allows to revert this. + allows one to revert this. * gpg: New import option "show-only". @@ -2800,7 +3433,7 @@ Noteworthy changes in version 2.1.0 (2014-11-06) * 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. + allows one to copy and paste the fingerprint from the key listing. * gpg: The hash algorithm is now printed for signature records in key listings. @@ -3580,7 +4213,7 @@ 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 + * New configure option --enable-agent-only allows one to build and install just the agent. * Fixed a problem with the log file handling. @@ -3975,7 +4608,7 @@ Noteworthy changes in version 1.1.92 (2002-09-11) extension specified with --load-extension are checked, along with their enclosing directories. - * The configure option --with-static-rnd=auto allows to build gpg + * The configure option --with-static-rnd=auto allows one 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 @@ -4358,7 +4991,7 @@ Noteworthy changes in version 1.0.2 (2000-07-12) * 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 + allows one 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. diff --git a/README b/README index 97961369e..8412389a7 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ Copyright 1997-2019 Werner Koch Copyright 1998-2021 Free Software Foundation, Inc. - Copyright 2003-2023 g10 Code GmbH + Copyright 2003-2024 g10 Code GmbH * INTRODUCTION @@ -25,9 +25,12 @@ can be freely used, modified and distributed under the terms of the GNU General Public License. + Note that versions 2.5.x are maintained development versions leading + to the forthcoming new stable version 2.6.x. + * BUILD INSTRUCTIONS - GnuPG 2.4 depends on the following GnuPG related packages: + GnuPG 2.6 depends on the following GnuPG related packages: npth (https://gnupg.org/ftp/gcrypt/npth/) libgpg-error (https://gnupg.org/ftp/gcrypt/libgpg-error/) @@ -40,7 +43,7 @@ Several other standard libraries are also required. The configure script prints diagnostic messages if one of these libraries is not - available and a feature will not be available.. + available and a feature will not be available. You also need the Pinentry package for most functions of GnuPG; however it is not a build requirement. Pinentry is available at @@ -80,15 +83,48 @@ to view the directories used by GnuPG. +** Quick build method on Unix + To quickly build all required software without installing it, the - Speedo method may be used: + Speedo target may be used. But first you need to make sure that the + toolchain is installed. On a Debian based system it should be + sufficient to run as root: - cd build - make -f ../build-aux/speedo.mk native + apt-get install build-essential libusb-1.0-0-dev libsqlite3-dev \ + libldap-dev libreadline-dev patchelf - This method downloads all required libraries and does a native build - of GnuPG to PLAY/inst/. GNU make is required and you need to set - LD_LIBRARY_PATH to $(pwd)/PLAY/inst/lib to test the binaries. + (libldap-dev and libreadline-dev are not strictly necessary but + are highly suggested.) + + Then as regular user run + + make -f build-aux/speedo.mk native + + This target downloads all required libraries and does a native build + of GnuPG to PLAY/inst/. After the build the entire software + including all libraries can be installed into an arbitrary location + using for example: + + make -f build-aux/speedo.mk install SYSROOT=/usr/local/gnupg26 + + and run the binaries like + + /usr/local/gnupg26/bin/gpg + + which will also start any daemon from the same directory. Make sure + to stop already running daemons or use a different GNUPGHOME. + + If you want to use the gnupg-w32-n.m.n_somedate.tar.xz tarball you + only need to change the first make invocation to + + make -f build-aux/speedo.mk this-native + + The advantage of this alternative tarball is that all libraries are + included and thus the Makefile does not need to download new + tarballs. Note that in any case all downloaded files come with + signatures which are verified by the Makefile commands. The + patchelf command is required to change the search path for the + shared libraries in the binaries to relative directories. ** Specific build problems on some machines: @@ -102,6 +138,13 @@ Add other options as needed. +*** Cygwin + + Although Cygwin (Posix emulation on top of Windows) is not + officially supported, GnuPG can be build for that platform. It + might be required to invoke configure like this: + + ./configure ac_cv_type_SOCKET=no *** Systems without a full C99 compiler @@ -144,6 +187,13 @@ gpg --import --import-options restore < allkeys.gpg gpgsm --import < allcerts.crt + In case the keyboxd is not able to startup due to a stale lockfile + created by another host, the command + + gpgconf --unlock pubring.db + + can be used to remove the lock file. + ** Socket directory GnuPG uses Unix domain sockets to connect its components (on Windows @@ -166,11 +216,44 @@ fi done ) +** Conflicts with systemd socket activation + + Some Linux distribution use the meanwhile deprecated --supervised + option with gpg-agent, dirmngr, and keyboxd. The idea is that the + systemd process launches the daemons as soon as gpg or gpgsm try to + access them. However, this creates a race condition with GnuPG's + own on-demand launching of these daemon. It also conflicts with the + remote use gpg-agent because the no-autostart feature on the remote + site will not work as expected. + + If your systems already comes with a systemd enabled GnuPG, you + should thus tell it not to start its own GnuPG daemons by running + the following three commands once: + + systemctl --user mask --now gpg-agent.service \ + gpg-agent.socket gpg-agent-ssh.socket \ + gpg-agent-extra.socket gpg-agent-browser.socket + systemctl --user mask --now dirmngr.service dirmngr.socket + systemctl --user mask --now keyboxd.service keyboxd.socket + + This way all GnuPG components can handle the startup of their + daemons on their own and start the correct version. + + The only problem is that for using GnuPG's ssh-agent protocol + support, the gpg-agent must have been started before ssh. This can + either be done with an ssh wrapper running + + gpg-connect-agent updatestartuptty /bye + + for each new tty or by using that command directly after login when + the anyway required SSH_AUTH_SOCK envvar is set (see the example in + the gpg-agent man page). + * DOCUMENTATION The complete documentation is in the texinfo manual named - `gnupg.info'. Run "info gnupg" to read it. If you want a a + `gnupg.info'. Run "info gnupg" to read it. If you want a printable copy of the manual, change to the "doc" directory and enter "make pdf" For a HTML version enter "make html" and point your browser to gnupg.html/index.html. Standard man pages for all diff --git a/acinclude.m4 b/acinclude.m4 index 98a87f673..d0d8e7e15 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -53,67 +53,6 @@ AC_DEFUN([GNUPG_CHECK_GNUMAKE], ]) -dnl GNUPG_CHECK_ENDIAN -dnl define either LITTLE_ENDIAN_HOST or BIG_ENDIAN_HOST -dnl -AC_DEFUN([GNUPG_CHECK_ENDIAN], - [ - tmp_assumed_endian=big - tmp_assume_warn="" - if test "$cross_compiling" = yes; then - case "$host_cpu" in - i@<:@345678@:>@* ) - tmp_assumed_endian=little - ;; - *) - ;; - esac - fi - AC_MSG_CHECKING(endianness) - AC_CACHE_VAL(gnupg_cv_c_endian, - [ gnupg_cv_c_endian=unknown - # See if sys/param.h defines the BYTE_ORDER macro. - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include - #include ]], [[ - #if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN - bogus endian macros - #endif]])], [# It does; now see whether it defined to BIG_ENDIAN or not. - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include - #include ]], [[ - #if BYTE_ORDER != BIG_ENDIAN - not big endian - #endif]])], gnupg_cv_c_endian=big, gnupg_cv_c_endian=little)]) - if test "$gnupg_cv_c_endian" = unknown; then - AC_RUN_IFELSE([AC_LANG_SOURCE([[main () { - /* Are we little or big endian? From Harbison&Steele. */ - union - { - long l; - char c[sizeof (long)]; - } u; - u.l = 1; - exit (u.c[sizeof (long) - 1] == 1); - }]])], - gnupg_cv_c_endian=little, - gnupg_cv_c_endian=big, - gnupg_cv_c_endian=$tmp_assumed_endian - tmp_assumed_warn=" (assumed)" - ) - fi - ]) - AC_MSG_RESULT([${gnupg_cv_c_endian}${tmp_assumed_warn}]) - if test "$gnupg_cv_c_endian" = little; then - AC_DEFINE(LITTLE_ENDIAN_HOST,1, - [Defined if the host has little endian byte ordering]) - else - AC_DEFINE(BIG_ENDIAN_HOST,1, - [Defined if the host has big endian byte ordering]) - fi - ]) - - - - # GNUPG_BUILD_PROGRAM(NAME,DEFAULT) # Add a --enable-NAME option to configure an set the # shell variable build_NAME either to "yes" or "no". DEFAULT must diff --git a/agent/Makefile.am b/agent/Makefile.am index 4da1ea9d8..b2a32b1fd 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -1,3 +1,4 @@ +# Makefile.am - agent # Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc. # # This file is part of GnuPG. @@ -23,8 +24,8 @@ libexec_PROGRAMS = gpg-protect-tool libexec_PROGRAMS += gpg-preset-passphrase noinst_PROGRAMS = $(TESTS) -EXTRA_DIST = ChangeLog-2011 gpg-agent-w32info.rc all-tests.scm - +EXTRA_DIST = ChangeLog-2011 all-tests.scm \ + gpg-agent-w32info.rc gpg-agent.w32-manifest.in AM_CPPFLAGS = @@ -32,6 +33,8 @@ include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM resource_objs += gpg-agent-w32info.o + +gpg-agent-w32info.o : gpg-agent.w32-manifest ../common/w32info-rc.h endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) diff --git a/agent/agent.h b/agent/agent.h index 4e7452eee..e891981b2 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -86,8 +86,8 @@ struct /* 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 start as pinentry (malloced). */ + char *pinentry_program; /* Filename of the program to handle daemon tasks. */ const char *daemon_program[DAEMON_MAX_TYPE]; @@ -121,7 +121,7 @@ struct /* Flag disallowing bypassing of the warning. */ int enforce_passphrase_constraints; - /* The require minmum length of a passphrase. */ + /* The required minimum length of a passphrase. */ unsigned int min_passphrase_len; /* The minimum number of non-alpha characters in a passphrase. */ @@ -225,6 +225,17 @@ typedef struct ssh_control_file_s *ssh_control_file_t; /* Forward reference for local definitions in call-scd.c. */ struct daemon_local_s; +/* Object to hold ephemeral secret keys. */ +struct ephemeral_private_key_s +{ + struct ephemeral_private_key_s *next; + unsigned char grip[KEYGRIP_LEN]; + unsigned char *keybuf; /* Canon-s-exp with the private key (malloced). */ + size_t keybuflen; +}; +typedef struct ephemeral_private_key_s *ephemeral_private_key_t; + + /* Collection of data per session (aka connection). */ struct server_control_s { @@ -246,6 +257,12 @@ struct server_control_s /* Private data of the daemon (call-XXX.c). */ struct daemon_local_s *d_local[DAEMON_MAX_TYPE]; + /* Linked list with ephemeral stored private keys. */ + ephemeral_private_key_t ephemeral_keys; + + /* If set functions will lookup keys in the ephemeral_keys list. */ + int ephemeral_mode; + /* Environment settings for the connection. */ session_env_t session_env; char *lc_ctype; @@ -269,10 +286,13 @@ struct server_control_s int algo; unsigned char value[MAX_DIGEST_LEN]; unsigned int raw_value: 1; - unsigned int is_pss: 1; /* DATA holds PSS formated data. */ + unsigned int is_pss: 1; /* DATA holds PSS formatted data. */ } digest; + unsigned int have_keygrip: 1; + unsigned int have_keygrip1: 1; unsigned char keygrip[20]; - int have_keygrip; + unsigned char keygrip1[20]; /* Another keygrip for hybrid crypto. */ + /* A flag to enable a hack to send the PKAUTH command instead of the PKSIGN command to the scdaemon. */ @@ -411,6 +431,7 @@ void *get_agent_daemon_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); +void agent_kick_the_loop (void); /*-- command.c --*/ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, @@ -452,10 +473,13 @@ 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, - time_t timestamp); +gpg_error_t agent_write_private_key (ctrl_t ctrl, + const unsigned char *grip, + const void *buffer, size_t length, + int force, + const char *serialno, const char *keyref, + const char *dispserialno, + time_t timestamp); gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, @@ -475,7 +499,7 @@ gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl, gcry_sexp_t *result, int *r_order); int agent_pk_get_algo (gcry_sexp_t s_key); int agent_is_tpm2_key(gcry_sexp_t s_key); -int agent_key_available (const unsigned char *grip); +int agent_key_available (ctrl_t ctrl, 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, @@ -483,7 +507,8 @@ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, 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 agent_update_private_key (const unsigned char *grip, nvc_t pk); +gpg_error_t agent_update_private_key (ctrl_t ctrl, + const unsigned char *grip, nvc_t pk); /*-- call-pinentry.c --*/ void initialize_module_call_pinentry (void); @@ -512,7 +537,7 @@ int agent_clear_passphrase (ctrl_t ctrl, /*-- cache.c --*/ void initialize_module_cache (void); void deinitialize_module_cache (void); -void agent_cache_housekeeping (void); +struct timespec *agent_cache_expiration (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); @@ -535,19 +560,37 @@ gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf, int *r_padding); +enum kemids + { + KEM_PQC_PGP, + KEM_PGP, + KEM_CMS + }; + +gpg_error_t agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid, + const unsigned char *ct, size_t ctlen, + const unsigned char *option, size_t optionlen, + membuf_t *outbuf); + /*-- genkey.c --*/ #define CHECK_CONSTRAINTS_NOT_EMPTY 1 #define CHECK_CONSTRAINTS_NEW_SYMKEY 2 +#define GENKEY_FLAG_NO_PROTECTION 1 +#define GENKEY_FLAG_PRESET 2 + +void clear_ephemeral_keys (ctrl_t ctrl); + int check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags, 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, time_t timestamp, +int agent_genkey (ctrl_t ctrl, unsigned int flags, + const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparmlen, - int no_protection, const char *override_passphrase, - int preset, membuf_t *outbuf); + const char *override_passphrase, + membuf_t *outbuf); gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); @@ -585,15 +628,17 @@ gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen); -gpg_error_t agent_write_shadow_key (const unsigned char *grip, +gpg_error_t agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip, const char *serialno, const char *keyid, - const unsigned char *pkbuf, int force); + const unsigned char *pkbuf, int force, + const char *dispserialno); /*-- 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_listtrusted (ctrl_t ctrl, void *assuan_context, + int status_mode); gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); @@ -610,6 +655,9 @@ int divert_tpm2_pkdecrypt (ctrl_t ctrl, char **r_buf, size_t *r_len, int *r_padding); int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t s_skey); +int agent_tpm2d_ecc_kem (ctrl_t ctrl, const unsigned char *shadow_info, + const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh); #else /*!HAVE_LIBTSS*/ static inline int divert_tpm2_pksign (ctrl_t ctrl, @@ -641,6 +689,16 @@ divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, (void)ctrl; (void)grip; (void)s_skey; return gpg_error (GPG_ERR_NOT_SUPPORTED); } + +static inline int +agent_tpm2d_ecc_kem (ctrl_t ctrl, const unsigned char *shadow_info, + const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh) +{ + (void)ctrl; (void)ecc_ct; + (void)ecc_point_len; (void)ecc_ecdh; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} #endif /*!HAVE_LIBTSS*/ @@ -661,8 +719,11 @@ gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *keyref, const char *keydata, size_t keydatalen); +gpg_error_t agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh); + /*-- call-daemon.c --*/ -gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl); +gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl, int req_sock); assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl); gpg_error_t daemon_unlock (enum daemon_type type, ctrl_t ctrl, gpg_error_t rc); void initialize_module_daemon (void); @@ -683,7 +744,7 @@ int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, char **r_buf, size_t *r_len); /*-- call-scd.c --*/ -int agent_card_learn (ctrl_t ctrl, +int agent_card_learn (ctrl_t ctrl, const char *demand_sn, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), @@ -709,6 +770,7 @@ int agent_card_pkdecrypt (ctrl_t ctrl, 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, @@ -730,9 +792,9 @@ 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); - /*-- learncard.c --*/ -int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force); +int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, + int force, const char *demand_sn); /*-- cvt-openpgp.c --*/ diff --git a/agent/cache.c b/agent/cache.c index 7616dafc1..0a4a6fbbc 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -53,8 +53,20 @@ struct secret_data_s { char data[1]; /* A string. */ }; -/* The cache object. */ +/* The type of cache object. */ typedef struct cache_item_s *ITEM; + +/* The timer entry in a linked list. */ +struct timer_s { + ITEM next; + int tv_sec; + int reason; +}; +#define CACHE_EXPIRE_UNUSED 0 +#define CACHE_EXPIRE_LAST_ACCESS 1 +#define CACHE_EXPIRE_CREATION 2 + +/* The cache object. */ struct cache_item_s { ITEM next; time_t created; @@ -63,12 +75,18 @@ struct cache_item_s { struct secret_data_s *pw; cache_mode_t cache_mode; int restricted; /* The value of ctrl->restricted is part of the key. */ + struct timer_s t; char key[1]; }; /* The cache himself. */ static ITEM thecache; +/* The timer list of expiration, in active. */ +static ITEM the_timer_list; +/* Newly created entries, to be inserted into the timer list. */ +static ITEM the_timer_list_new; + /* NULL or the last cache key stored by agent_store_cache_hit. */ static char *last_stored_cache_key; @@ -193,100 +211,298 @@ new_data (const char *string, struct secret_data_s **r_data) } - -/* Check whether there are items to expire. */ static void -housekeeping (void) +insert_to_timer_list_new (ITEM entry) { - ITEM r, rprev; + entry->t.next = the_timer_list_new; + the_timer_list_new = entry; +} + +/* Insert to the active timer list. */ +static void +insert_to_timer_list (struct timespec *ts, ITEM entry) +{ + ITEM e, eprev; + + if (!the_timer_list || ts->tv_sec >= entry->t.tv_sec) + { + if (the_timer_list) + { + the_timer_list->t.tv_sec += ts->tv_sec - entry->t.tv_sec; + if (ts->tv_nsec >= 500000000) + the_timer_list->t.tv_sec++; + } + + ts->tv_sec = entry->t.tv_sec; + ts->tv_nsec = 0; + + entry->t.tv_sec = 0; + entry->t.next = the_timer_list; + the_timer_list = entry; + return; + } + + entry->t.tv_sec -= ts->tv_sec; + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) + { + if (e->t.tv_sec > entry->t.tv_sec) + break; + + eprev = e; + entry->t.tv_sec -= e->t.tv_sec; + } + + entry->t.next = e; + if (e) + e->t.tv_sec -= entry->t.tv_sec; + + if (eprev) + eprev->t.next = entry; + else + the_timer_list = entry; +} + +static void +remove_from_timer_list (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (e->t.next) + e->t.next->t.tv_sec += e->t.tv_sec; + + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static void +remove_from_timer_list_new (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list_new; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list_new = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static int +compute_expiration (ITEM r) +{ + unsigned long maxttl; time_t current = gnupg_get_time (); + time_t next; - /* First expire the actual data */ - for (r=thecache; r; r = r->next) + if (r->cache_mode == CACHE_MODE_PIN) + return 0; /* Don't let it expire - scdaemon explicitly flushes them. */ + + if (!r->pw) { - if (r->cache_mode == CACHE_MODE_PIN) - ; /* 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; - } + /* Expire an old and unused entry after 30 minutes. */ + r->t.tv_sec = 60*30; + r->t.reason = CACHE_EXPIRE_UNUSED; + return 1; } - /* 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) + if (r->cache_mode == CACHE_MODE_DATA) { - unsigned long maxttl; - - switch (r->cache_mode) + /* No MAX TTL here. */ + if (r->ttl >= 0) { - 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; + r->t.tv_sec = r->ttl; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; } else - { - rprev = r; - r = r->next; - } + return 0; + } + else if (r->cache_mode == CACHE_MODE_SSH) + maxttl = opt.max_cache_ttl_ssh; + else + maxttl = opt.max_cache_ttl; + + if (r->created + maxttl <= current) + { + r->t.tv_sec = 0; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; + } + + next = r->created + maxttl - current; + if (r->ttl >= 0 && r->ttl < next) + { + r->t.tv_sec = r->ttl; + r->t.reason = CACHE_EXPIRE_LAST_ACCESS; + return 1; + } + + r->t.tv_sec = next; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; +} + +static void +update_expiration (ITEM entry, int is_new_entry) +{ + if (!is_new_entry) + { + remove_from_timer_list (entry); + remove_from_timer_list_new (entry); + } + + if (compute_expiration (entry)) + { + insert_to_timer_list_new (entry); + agent_kick_the_loop (); } } -void -agent_cache_housekeeping (void) +/* Expire the cache entry. Returns 1 when the entry should be removed + * from the cache. */ +static int +do_expire (ITEM e) { - int res; + if (!e->pw) + /* Unused entry after 30 minutes. */ + return 1; - if (DBG_CACHE) - log_debug ("agent_cache_housekeeping\n"); + if (e->t.reason == CACHE_EXPIRE_LAST_ACCESS) + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%ds after last access)\n", + e->key, e->restricted, e->ttl); + } + else + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%lus after creation)\n", + e->key, e->restricted, opt.max_cache_ttl); + } + + release_data (e->pw); + e->pw = NULL; + e->accessed = 0; + + if (compute_expiration (e)) + insert_to_timer_list_new (e); + + return 0; +} + + +struct timespec * +agent_cache_expiration (void) +{ + static struct timespec abstime; + static struct timespec timeout; + struct timespec *tp; + struct timespec curtime; + int res; + int expired = 0; + ITEM e, enext; res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); - housekeeping (); + npth_clock_gettime (&curtime); + if (the_timer_list) + { + if (npth_timercmp (&abstime, &curtime, <)) + expired = 1; + else + npth_timersub (&abstime, &curtime, &timeout); + } + + if (expired && (e = the_timer_list) && e->t.tv_sec == 0) + { + the_timer_list = e->t.next; + e->t.next = NULL; + + if (do_expire (e)) + { + ITEM r, rprev; + + if (DBG_CACHE) + log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", + e->key, e->restricted, e->cache_mode); + + rprev = NULL; + for (r = thecache; r; r = r->next) + if (r == e) + { + if (!rprev) + thecache = r->next; + else + rprev->next = r->next; + break; + } + else + rprev = r; + + remove_from_timer_list_new (e); + + xfree (e); + } + } + + if (expired || !the_timer_list) + timeout.tv_sec = timeout.tv_nsec = 0; + + for (e = the_timer_list_new; e; e = enext) + { + enext = e->t.next; + e->t.next = NULL; + insert_to_timer_list (&timeout, e); + } + the_timer_list_new = NULL; + + if (!the_timer_list) + tp = NULL; + else + { + if (the_timer_list->t.tv_sec != 0) + { + timeout.tv_sec += the_timer_list->t.tv_sec; + the_timer_list->t.tv_sec = 0; + } + + npth_timeradd (&timeout, &curtime, &abstime); + tp = &timeout; + } res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); + + return tp; } @@ -314,6 +530,7 @@ agent_flush_cache (int pincache_only) release_data (r->pw); r->pw = NULL; r->accessed = 0; + update_expiration (r, 0); } } @@ -358,7 +575,6 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, if (DBG_CACHE) log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n", key, restricted, cache_mode, ttl); - housekeeping (); if (!ttl) { @@ -410,6 +626,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, err = new_data (data, &r->pw); if (err) log_error ("error replacing cache item: %s\n", gpg_strerror (err)); + update_expiration (r, 0); } } else if (data) /* Insert. */ @@ -431,6 +648,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, { r->next = thecache; thecache = r; + update_expiration (r, 1); } } if (err) @@ -478,7 +696,6 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n", key, restricted, cache_mode, last_stored? " (stored cache key)":""); - housekeeping (); for (r=thecache; r; r = r->next) { @@ -500,7 +717,10 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) * 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 (); + { + r->accessed = gnupg_get_time (); + update_expiration (r, 0); + } if (DBG_CACHE) log_debug ("... hit\n"); if (r->pw->totallen < 32) diff --git a/agent/call-daemon.c b/agent/call-daemon.c index 0c3605274..f7ea0dd51 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -56,9 +56,6 @@ struct daemon_local_s DAEMON_LOCAL_LIST (see below). */ struct daemon_local_s *next_local; - /* Link back to the global structure. */ - struct daemon_global_s *g; - assuan_context_t ctx; /* NULL or session context for the daemon used with this connection. */ unsigned int in_use: 1; /* CTX is in use. */ @@ -98,7 +95,6 @@ static npth_mutex_t start_daemon_lock; struct wait_child_thread_parm_s { enum daemon_type type; - pid_t pid; }; @@ -109,52 +105,14 @@ wait_child_thread (void *arg) int err; struct wait_child_thread_parm_s *parm = arg; enum daemon_type type = parm->type; - pid_t pid = parm->pid; -#ifndef HAVE_W32_SYSTEM - int wstatus; -#endif const char *name = opt.daemon_program[type]; struct daemon_global_s *g = &daemon_global[type]; struct daemon_local_s *sl; xfree (parm); /* We have copied all data to the stack. */ -#ifdef HAVE_W32_SYSTEM - npth_unprotect (); - /* Note that although we use a pid_t here, it is actually a HANDLE. */ - WaitForSingleObject ((HANDLE)pid, INFINITE); - npth_protect (); + assuan_pipe_wait_server_termination (g->primary_ctx, NULL, 0); log_info ("daemon %s finished\n", name); -#else /* !HAVE_W32_SYSTEM*/ - - again: - npth_unprotect (); - err = waitpid (pid, &wstatus, 0); - npth_protect (); - - if (err < 0) - { - if (errno == EINTR) - goto again; - log_error ("waitpid for %s failed: %s\n", name, strerror (errno)); - return NULL; - } - else - { - if (WIFEXITED (wstatus)) - log_info ("daemon %s finished (status %d)\n", - name, WEXITSTATUS (wstatus)); - else if (WIFSIGNALED (wstatus)) - log_info ("daemon %s killed by signal %d\n", name, WTERMSIG (wstatus)); - else - { - if (WIFSTOPPED (wstatus)) - log_info ("daemon %s stopped by signal %d\n", - name, WSTOPSIG (wstatus)); - goto again; - } - } -#endif /*!HAVE_W32_SYSTEM*/ agent_flush_cache (1); /* Flush the PIN cache. */ @@ -166,8 +124,6 @@ wait_child_thread (void *arg) } else { - assuan_set_flag (g->primary_ctx, ASSUAN_NO_WAITPID, 1); - for (sl = g->local_list; sl; sl = sl->next_local) { sl->invalid = 1; @@ -178,6 +134,9 @@ wait_child_thread (void *arg) } } + if (g->primary_ctx_reusable) + assuan_release (g->primary_ctx); + g->primary_ctx = NULL; g->primary_ctx_reusable = 0; @@ -255,7 +214,7 @@ atfork_cb (void *opaque, int where) * caller must call unlock_daemon after this function has returned * success and the actual Assuan transaction been done. */ gpg_error_t -daemon_start (enum daemon_type type, ctrl_t ctrl) +daemon_start (enum daemon_type type, ctrl_t ctrl, int require_socket) { gpg_error_t err = 0; const char *pgmname; @@ -287,6 +246,7 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) return gpg_error (GPG_ERR_INTERNAL); } + again: /* We need to serialize the access to scd_local_list and primary_scd_ctx. */ rc = npth_mutex_lock (&start_daemon_lock); if (rc) @@ -310,7 +270,6 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) strerror (rc)); return err; } - ctrl->d_local[type]->g = g; ctrl->d_local[type]->next_local = g->local_list; g->local_list = ctrl->d_local[type]; /* FIXME: CHECK the G thing */ } @@ -320,7 +279,7 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ - if (g->primary_ctx && g->primary_ctx_reusable) + if (g->primary_ctx && g->primary_ctx_reusable && !require_socket) { ctx = g->primary_ctx; g->primary_ctx_reusable = 0; @@ -471,8 +430,8 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) char buf[100]; #ifdef HAVE_W32_SYSTEM - snprintf (buf, sizeof buf, "OPTION event-signal=%lx", - (unsigned long)get_agent_daemon_notify_event ()); + snprintf (buf, sizeof buf, "OPTION event-signal=%p", + get_agent_daemon_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif @@ -496,7 +455,6 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) } wctp->type = type; - wctp->pid = assuan_get_pid (g->primary_ctx); err = npth_attr_init (&tattr); if (!err) { @@ -511,13 +469,13 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) } leave: - rc = npth_mutex_unlock (&start_daemon_lock); - if (rc) - log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); - xfree (abs_homedir); + abs_homedir = NULL; if (err) { + rc = npth_mutex_unlock (&start_daemon_lock); + if (rc) + log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); daemon_unlock (type, ctrl, err); if (ctx) assuan_release (ctx); @@ -526,6 +484,15 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) { ctrl->d_local[type]->ctx = ctx; ctrl->d_local[type]->invalid = 0; + rc = npth_mutex_unlock (&start_daemon_lock); + if (rc) + log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); + + if (require_socket && g->primary_ctx == ctx) + { + daemon_unlock (type, ctrl, 0); + goto again; + } } return err; } @@ -561,10 +528,9 @@ agent_daemon_dump_state (void) for (i = 0; i < DAEMON_MAX_TYPE; i++) { struct daemon_global_s *g = &daemon_global[i]; - log_info ("%s: name %s primary_ctx=%p pid=%ld reusable=%d\n", __func__, + log_info ("%s: name %s primary_ctx=%p reusable=%d\n", __func__, gnupg_module_name (daemon_modules[i]), g->primary_ctx, - (long)assuan_get_pid (g->primary_ctx), g->primary_ctx_reusable); if (g->socket_name) log_info ("%s: socket='%s'\n", __func__, g->socket_name); @@ -619,7 +585,7 @@ agent_reset_daemon (ctrl_t ctrl) for (i = 0; i < DAEMON_MAX_TYPE; i++) if (ctrl->d_local[i]) { - struct daemon_global_s *g = ctrl->d_local[i]->g; + struct daemon_global_s *g = &daemon_global[i]; if (ctrl->d_local[i]->ctx) { diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 656d5f623..ba37a775e 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -128,8 +128,9 @@ initialize_module_call_pinentry (void) void agent_query_dump_state (void) { - log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", - entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%lx\n", + entry_ctx, (long)assuan_get_pid (entry_ctx), + (unsigned long)popup_tid); } /* Called to make sure that a popup window owned by the current @@ -174,6 +175,7 @@ unlock_pinentry (ctrl_t ctrl, gpg_error_t rc) case GPG_ERR_NO_PASSPHRASE: case GPG_ERR_BAD_PASSPHRASE: case GPG_ERR_BAD_PIN: + case GPG_ERR_BAD_RESET_CODE: break; case GPG_ERR_CORRUPTED_PROTECTION: @@ -882,7 +884,7 @@ struct inq_cb_parm_s }; -/* Return true if PIN is indentical to the last generated pin. */ +/* Return true if PIN is identical to the last generated pin. */ static int is_generated_pin (struct inq_cb_parm_s *parm, const char *pin) { @@ -1287,8 +1289,6 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) static void * watch_sock (void *arg) { - pid_t pid = assuan_get_pid (entry_ctx); - while (1) { int err; @@ -1301,7 +1301,7 @@ watch_sock (void *arg) FD_ZERO (&fdset); FD_SET (FD2INT (sock), &fdset); - err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); + err = npth_select (FD2NUM (sock)+1, &fdset, NULL, NULL, &timeout); if (err < 0) { @@ -1316,17 +1316,7 @@ watch_sock (void *arg) break; } - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) - TerminateProcess ((HANDLE)pid, 1); -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + assuan_pipe_kill_server (entry_ctx); return NULL; } @@ -1621,12 +1611,13 @@ agent_askpin (ctrl_t ctrl, && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN + || gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; - else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + else errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) @@ -1894,12 +1885,13 @@ agent_get_passphrase (ctrl_t ctrl, if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) return unlock_pinentry (ctrl, rc); - if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN + || gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE) { if (pininfo->cb_errtext) errtext = pininfo->cb_errtext; - else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE - || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + else errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); } else if (rc) @@ -2121,7 +2113,6 @@ void agent_popup_message_stop (ctrl_t ctrl) { int rc; - pid_t pid; (void)ctrl; @@ -2134,26 +2125,10 @@ agent_popup_message_stop (ctrl_t ctrl) return; } - pid = assuan_get_pid (entry_ctx); - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ - else if (popup_finished) + if (popup_finished) ; /* Already finished and ready for joining. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE - && pid != 0) - { - HANDLE process = (HANDLE) pid; - - /* Arbitrary error code. */ - TerminateProcess (process, 1); - } -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + else + assuan_pipe_kill_server (entry_ctx); /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); diff --git a/agent/call-scd.c b/agent/call-scd.c index 91e28e68c..28669206c 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -94,7 +94,7 @@ struct inq_needpin_parm_s static int start_scd (ctrl_t ctrl) { - return daemon_start (DAEMON_SCD, ctrl); + return daemon_start (DAEMON_SCD, ctrl, 0); } @@ -151,15 +151,17 @@ handle_pincache_put (const char *args) pin = s; if (!*pin) { - /* No value - flush the cache. The cache module knows aboput + /* No value - flush the cache. The cache module knows about * the structure of the key to flush only parts. */ - log_debug ("%s: flushing cache '%s'\n", __func__, key); + if (DBG_CACHE) + log_debug ("%s: flushing cache '%s'\n", __func__, key); agent_put_cache (NULL, key, CACHE_MODE_PIN, NULL, -1); err = 0; goto leave; } - log_debug ("%s: caching '%s'->'%s'\n", __func__, key, pin); + if (DBG_CACHE) + log_debug ("%s: caching '%s'->'%s'\n", __func__, key, "[hidden]"); agent_put_cache (NULL, key, CACHE_MODE_PIN, pin, -1); err = 0; @@ -196,7 +198,8 @@ handle_pincache_get (const char *args, assuan_context_t ctx) const char *key; char *pin = NULL; - log_debug ("%s: enter '%s'\n", __func__, args); + if (DBG_CACHE) + log_debug ("%s: enter '%s'\n", __func__, args); key = args; if (strlen (key) < 5) { @@ -210,11 +213,14 @@ handle_pincache_get (const char *args, assuan_context_t ctx) if (!pin || !*pin) { xfree (pin); + pin = NULL; err = 0; /* Not found is indicated by sending no data back. */ - log_debug ("%s: not cached\n", __func__); + if (DBG_CACHE) + log_debug ("%s: not cached\n", __func__); goto leave; } - log_debug ("%s: cache returned '%s'\n", __func__, pin); + if (DBG_CACHE) + log_debug ("%s: cache returned '%s'\n", __func__, "[hidden]"/*pin*/); err = assuan_send_data (ctx, pin, strlen (pin)); leave: @@ -254,10 +260,14 @@ learn_status_cb (void *opaque, const char *line) return err; } + /* Perform the LEARN command and return a list of all private keys - stored on the card. */ + * stored on the card. If DEMAND_SN is given the info is returned for + * the card with that S/N instead of the current card. This may then + * switch the current card. */ int agent_card_learn (ctrl_t ctrl, + const char *demand_sn, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), @@ -267,6 +277,7 @@ agent_card_learn (ctrl_t ctrl, { int rc; struct learn_parm_s parm; + char line[ASSUAN_LINELENGTH]; rc = start_scd (ctrl); if (rc) @@ -279,7 +290,13 @@ agent_card_learn (ctrl_t ctrl, parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; - rc = assuan_transact (daemon_ctx (ctrl), "LEARN --force", + + if (demand_sn && *demand_sn) + snprintf (line, sizeof line, "LEARN --demand=%s --force", demand_sn); + else + snprintf (line, sizeof line, "LEARN --force"); + + rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) @@ -465,6 +482,33 @@ hash_algo_option (int algo) } +static int +prepare_setdata (ctrl_t ctrl, const unsigned char *indata, size_t indatalen) +{ + int rc; + char *p, line[ASSUAN_LINELENGTH]; + size_t len; + int i; + + for (len = 0; len < indatalen;) + { + p = stpcpy (line, "SETDATA "); + if (len) + p = stpcpy (p, "--append "); + for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++) + { + sprintf (p, "%02X", indata[len]); + p += 2; + } + rc = assuan_transact (daemon_ctx (ctrl), line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return rc; + } + + return 0; +} + /* Create a signature using the current card. MDALGO is either 0 or * gives the digest algorithm. DESC_TEXT is an additional parameter * passed to GETPIN_CB. */ @@ -494,13 +538,7 @@ agent_card_pksign (ctrl_t ctrl, if (!mdalgo) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); - if (indatalen*2 + 50 > DIM(line)) - return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); - - bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); - - rc = assuan_transact (daemon_ctx (ctrl), line, - NULL, NULL, NULL, NULL, pincache_put_cb, NULL); + rc = prepare_setdata (ctrl, indata, indatalen); if (rc) return unlock_scd (ctrl, rc); @@ -548,7 +586,8 @@ padding_info_cb (void *opaque, const char *line) if ((s=has_leading_keyword (line, "PADDING"))) { - *r_padding = atoi (s); + if (r_padding) + *r_padding = atoi (s); } else if ((s=has_leading_keyword (line, "PINCACHE_PUT"))) err = handle_pincache_put (s); @@ -560,8 +599,8 @@ padding_info_cb (void *opaque, const char *line) /* Decipher INDATA using the current card. Note that the returned * value is not an s-expression but the raw data as returned by * scdaemon. The padding information is stored at R_PADDING with -1 - * for not known. DESC_TEXT is an additional parameter passed to - * GETPIN_CB. */ + * for not known, when it's not NULL. DESC_TEXT is an additional + * parameter passed to GETPIN_CB. */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, @@ -572,35 +611,24 @@ agent_card_pkdecrypt (ctrl_t ctrl, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen, int *r_padding) { - int rc, i; - char *p, line[ASSUAN_LINELENGTH]; + int rc; + char line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_parm_s inqparm; size_t len; *r_buf = NULL; - *r_padding = -1; /* Unknown. */ + if (r_padding) + *r_padding = -1; /* Unknown. */ rc = start_scd (ctrl); if (rc) return rc; /* FIXME: use secure memory where appropriate */ - for (len = 0; len < indatalen;) - { - p = stpcpy (line, "SETDATA "); - if (len) - p = stpcpy (p, "--append "); - for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++) - { - sprintf (p, "%02X", indata[len]); - p += 2; - } - rc = assuan_transact (daemon_ctx (ctrl), line, - NULL, NULL, NULL, NULL, NULL, NULL); - if (rc) - return unlock_scd (ctrl, rc); - } + rc = prepare_setdata (ctrl, indata, indatalen); + if (rc) + return unlock_scd (ctrl, rc); init_membuf (&data, 1024); inqparm.ctx = daemon_ctx (ctrl); @@ -1147,6 +1175,100 @@ pass_data_thru (void *opaque, const void *buffer, size_t length) return 0; } +#define DEVINFO_WATCH_COMMAND "DEVINFO --watch" + +struct devinfo_watch_thread { + ctrl_t ctrl; + void *assuan_context; +}; + +static void * +devinfo_watch_thread (void *arg) +{ + struct devinfo_watch_thread *d = arg; + + assuan_set_flag (daemon_ctx (d->ctrl), ASSUAN_CONVEY_COMMENTS, 1); + assuan_transact (daemon_ctx (d->ctrl), DEVINFO_WATCH_COMMAND, + pass_data_thru, d->assuan_context, + NULL, NULL, + pass_status_thru, d->assuan_context); + return NULL; +} + +static int +agent_card_devinfo (ctrl_t ctrl, void *assuan_context) +{ + npth_t thread; + struct devinfo_watch_thread dwt; + gpg_error_t err = 0; + npth_attr_t tattr; + assuan_context_t scd_ctx; + gnupg_fd_t scd_fds[2]; + gnupg_fd_t client_input; + gnupg_fd_t scd_sock; + int rc; + gnupg_fd_t client_fds[2]; + + if (ctrl->thread_startup.fd == GNUPG_INVALID_FD) + return GPG_ERR_INV_HANDLE; + + rc = daemon_start (DAEMON_SCD, ctrl, 1); + if (rc) + return rc; + + dwt.ctrl = ctrl; + dwt.assuan_context = assuan_context; + scd_ctx = daemon_ctx (ctrl); + + err = npth_attr_init (&tattr); + if (err) + return err; + + npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); + err = npth_create (&thread, &tattr, devinfo_watch_thread, &dwt); + npth_attr_destroy (&tattr); + if (err) + { + log_error ("error spawning devinfo_watch_thread: %s\n", strerror (err)); + return err; + } + + assuan_get_active_fds (assuan_context, 0, client_fds, 2); + client_input = client_fds[0]; + + assuan_get_active_fds (scd_ctx, 0, scd_fds, 2); + scd_sock = scd_fds[0]; + + while (1) + { + fd_set fdset; + int nfd; + int ret; + + FD_ZERO (&fdset); + FD_SET (FD2INT (client_input), &fdset); + nfd = FD2NUM (client_input); + + ret = npth_select (nfd+1, &fdset, NULL, NULL, NULL); + if (ret == 1) + break; + } + + /* Forcibly close the socket connection to scdaemon. */ +#ifdef HAVE_W32_SYSTEM +# if _WIN64 + shutdown ((uintptr_t)scd_sock, SD_BOTH); +# else + shutdown ((unsigned int)scd_sock, SD_BOTH); +# endif +#else + shutdown (scd_sock, SHUT_RDWR); +#endif + /* Then, join the thread. */ + npth_join (thread, NULL); + + return unlock_scd (ctrl, rc); +} /* Send the line CMDLINE with command for the SCDdaemon to it and send all status messages back. This command is used as a general quoting @@ -1162,6 +1284,11 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline, struct inq_needpin_parm_s inqparm; int saveflag; + /* This is a layer violation, but it's needed that because DEVINFO + --watch is so special. */ + if (!strcmp (cmdline, DEVINFO_WATCH_COMMAND)) + return agent_card_devinfo (ctrl, assuan_context); + rc = start_scd (ctrl); if (rc) return rc; diff --git a/agent/call-tpm2d.c b/agent/call-tpm2d.c index 1048c7d63..3f6ca7b52 100644 --- a/agent/call-tpm2d.c +++ b/agent/call-tpm2d.c @@ -16,7 +16,7 @@ static int start_tpm2d (ctrl_t ctrl) { - return daemon_start (DAEMON_TPM2D, ctrl); + return daemon_start (DAEMON_TPM2D, ctrl, 0); } static int diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 51111a60d..6b872f1fa 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2430,11 +2430,16 @@ card_key_available (ctrl_t ctrl, const struct card_key_info_s *keyinfo, } hex2bin (keyinfo->keygrip, grip, sizeof (grip)); - if ( agent_key_available (grip) ) + if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip) ) { + char *dispserialno; + /* (Shadow)-key is not available in our key storage. */ - err = agent_write_shadow_key (grip, keyinfo->serialno, - keyinfo->idstr, pkbuf, 0); + agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, + keyinfo->keygrip); + err = agent_write_shadow_key (ctrl, grip, keyinfo->serialno, + keyinfo->idstr, pkbuf, 0, dispserialno); + xfree (dispserialno); if (err) { xfree (pkbuf); @@ -2580,7 +2585,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) struct card_key_info_s *keyinfo_on_cards, *l; char *cardsn; gcry_sexp_t key_public = NULL; - int count; + int count, skipped; struct key_collection_s keyarray = { NULL }; err = open_control_file (&cf, 0); @@ -2597,6 +2602,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) if (!dirname) { err = gpg_error_from_syserror (); + ssh_close_control_file (cf); agent_card_free_keyinfo (keyinfo_on_cards); return err; } @@ -2605,6 +2611,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) { err = gpg_error_from_syserror (); xfree (dirname); + ssh_close_control_file (cf); agent_card_free_keyinfo (keyinfo_on_cards); return err; } @@ -2709,6 +2716,8 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) err = add_to_key_array (&keyarray, key_public, cardsn, order); if (err) { + gnupg_closedir (dir); + ssh_close_control_file (cf); gcry_sexp_release (key_public); xfree (cardsn); goto leave; @@ -2744,6 +2753,7 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) keyarray.items[count].key, keyarray.items[count].cardsn); /* And print the keys. */ + skipped = 0; for (count=0; count < keyarray.nitems; count++) { err = ssh_send_key_public (key_blobs, keyarray.items[count].key, @@ -2758,12 +2768,13 @@ ssh_send_available_keys (ctrl_t ctrl, estream_t key_blobs, u32 *r_key_counter) /* For example a Brainpool curve or a curve we don't * support at all but a smartcard lists that curve. * We ignore them. */ + skipped++; } else goto leave; } } - *r_key_counter = count; + *r_key_counter = count - skipped; leave: agent_card_free_keyinfo (keyinfo_on_cards); @@ -3217,7 +3228,7 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, /* 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) ) + if ( !agent_key_available (ctrl, key_grip_raw) ) goto key_exists; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); @@ -3281,8 +3292,8 @@ ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, /* Store this key to our key storage. We do not store a creation * timestamp because we simply do not know. */ - err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, - NULL, NULL, 0); + err = agent_write_private_key (ctrl, key_grip_raw, buffer, buffer_n, 0, + NULL, NULL, NULL, 0); if (err) goto out; @@ -3947,7 +3958,11 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) es_syshd_t syshd; syshd.type = ES_SYSHD_SOCK; +#if defined(HAVE_SOCKET) && defined(HAVE_W32_SYSTEM) + syshd.u.sock = (SOCKET)sock_client; +#else syshd.u.sock = sock_client; +#endif get_client_info (sock_client, &peer_info); ctrl->client_pid = peer_info.pid; diff --git a/agent/command.c b/agent/command.c index dd7cb5e57..a9eb0104e 100644 --- a/agent/command.c +++ b/agent/command.c @@ -241,7 +241,7 @@ reset_notify (assuan_context_t ctx, char *line) (void) line; memset (ctrl->keygrip, 0, 20); - ctrl->have_keygrip = 0; + ctrl->have_keygrip = ctrl->have_keygrip1 = 0; ctrl->digest.valuelen = 0; xfree (ctrl->digest.data); ctrl->digest.data = NULL; @@ -251,6 +251,9 @@ reset_notify (assuan_context_t ctx, char *line) clear_nonce_cache (ctrl); + /* Note that a RESET does not clear the ephemeral store because + * clients are used to issue a RESET on a connection. */ + return 0; } @@ -566,22 +569,24 @@ cmd_istrusted (assuan_context_t ctx, char *line) static const char hlp_listtrusted[] = - "LISTTRUSTED\n" + "LISTTRUSTED [--status]\n" "\n" - "List all entries from the trustlist."; + "List all entries from the trustlist. With --status the\n" + "keys are listed using status line similar to ISTRUSTED"; static gpg_error_t cmd_listtrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); - int rc; + gpg_error_t err; + int opt_status; - (void)line; + opt_status = has_option (line, "--status"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - rc = agent_listtrusted (ctx); - return leave_cmd (ctx, rc); + err = agent_listtrusted (ctrl, ctx, opt_status); + return leave_cmd (ctx, err); } @@ -634,34 +639,65 @@ cmd_marktrusted (assuan_context_t ctx, char *line) static const char hlp_havekey[] = "HAVEKEY \n" "HAVEKEY --list[=]\n" + "HAVEKEY --info \n" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available. With --list return all available keygrips\n" - "as binary data; with bail out at this number of keygrips"; + "as binary data; with bail out at this number of keygrips.\n" + "In --info mode check just one keygrip."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { - ctrl_t ctrl; + ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; char *p; - int list_mode; /* Less than 0 for no limit. */ + int list_mode = 0; /* Less than 0 for no limit. */ + int info_mode = 0; int counter; - char *dirname; - gnupg_dir_t dir; + char *dirname = NULL; + gnupg_dir_t dir = NULL; gnupg_dirent_t dir_entry; char hexgrip[41]; struct card_key_info_s *keyinfo_on_cards, *l; - if (has_option_name (line, "--list")) + if (has_option (line, "--info")) + info_mode = 1; + else if (has_option_name (line, "--list")) { if ((p = option_value (line, "--list"))) list_mode = atoi (p); else list_mode = -1; } - else - list_mode = 0; + + line = skip_options (line); + + if (info_mode) + { + int keytype; + const char *infostring; + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + err = agent_key_info_from_file (ctrl, grip, &keytype, NULL, NULL); + if (err) + goto leave; + + switch (keytype) + { + case PRIVATE_KEY_CLEAR: + case PRIVATE_KEY_OPENPGP_NONE: infostring = "clear"; break; + case PRIVATE_KEY_PROTECTED: infostring = "protected"; break; + case PRIVATE_KEY_SHADOWED: infostring = "shadowed"; break; + default: infostring = "unknown"; break; + } + + err = agent_write_status (ctrl, "KEYFILEINFO", infostring, NULL); + goto leave; + } if (!list_mode) @@ -672,7 +708,7 @@ cmd_havekey (assuan_context_t ctx, char *line) if (err) return err; - if (!agent_key_available (grip)) + if (!agent_key_available (ctrl, grip)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') @@ -690,7 +726,6 @@ cmd_havekey (assuan_context_t ctx, char *line) /* List mode. */ dir = NULL; dirname = NULL; - ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) { @@ -763,8 +798,8 @@ cmd_havekey (assuan_context_t ctx, char *line) static const char hlp_sigkey[] = - "SIGKEY \n" - "SETKEY \n" + "SIGKEY [--another] \n" + "SETKEY [--another] \n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t @@ -772,11 +807,17 @@ cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); + int opt_another; - rc = parse_keygrip (ctx, line, ctrl->keygrip); + opt_another = has_option (line, "--another"); + line = skip_options (line); + rc = parse_keygrip (ctx, line, opt_another? ctrl->keygrip1 : ctrl->keygrip); if (rc) return rc; - ctrl->have_keygrip = 1; + if (opt_another) + ctrl->have_keygrip1 = 1; + else + ctrl->have_keygrip = 1; return 0; } @@ -1010,10 +1051,14 @@ cmd_pksign (assuan_context_t ctx, char *line) static const char hlp_pkdecrypt[] = - "PKDECRYPT []\n" + "PKDECRYPT [--kem[=] []\n" "\n" "Perform the actual decrypt operation. Input is not\n" - "sensitive to eavesdropping."; + "sensitive to eavesdropping.\n" + "If the --kem option is used, decryption is done with the KEM,\n" + "inquiring upper-layer option, when needed. KEMID can be\n" + "specified with --kem option; Valid value is: PQC-PGP, PGP, or CMS.\n" + "Default is PQC-PGP."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { @@ -1022,22 +1067,52 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line) unsigned char *value; size_t valuelen; membuf_t outbuf; - int padding; + int padding = -1; + unsigned char *option = NULL; + size_t optionlen = 0; + const char *p; + int kemid = -1; - (void)line; + p = has_option_name (line, "--kem"); + if (p) + { + kemid = KEM_PQC_PGP; + if (*p == '=') + { + p++; + if (!strcmp (p, "PQC-PGP")) + kemid = KEM_PQC_PGP; + else if (!strcmp (p, "PGP")) + kemid = KEM_PGP; + else if (!strcmp (p, "CMS")) + kemid = KEM_CMS; + else + return set_error (GPG_ERR_ASS_PARAMETER, "invalid KEM algorithm"); + } + } /* 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 && kemid > KEM_PGP) + rc = assuan_inquire (ctx, "OPTION", + &option, &optionlen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); - rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, - value, valuelen, &outbuf, &padding); + if (kemid < 0) + rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, + value, valuelen, &outbuf, &padding); + else + { + rc = agent_kem_decrypt (ctrl, ctrl->server_local->keydesc, kemid, + value, valuelen, option, optionlen, &outbuf); + xfree (option); + } xfree (value); if (rc) clear_outbuf (&outbuf); @@ -1083,26 +1158,29 @@ 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; const char *s; time_t opt_timestamp; int c; + unsigned int flags = 0; + + init_membuf (&outbuf, 512); 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"); + if (has_option (line, "--no-protection")) + flags |= GENKEY_FLAG_NO_PROTECTION; + if (has_option (line, "--preset")) + flags |= GENKEY_FLAG_PRESET; opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) @@ -1153,11 +1231,9 @@ cmd_genkey (assuan_context_t ctx, char *line) if (rc) goto leave; - 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) + if (opt_inq_passwd && !(flags & GENKEY_FLAG_NO_PROTECTION)) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); @@ -1170,16 +1246,17 @@ cmd_genkey (assuan_context_t ctx, char *line) /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; - no_protection = 1; + flags |= GENKEY_FLAG_NO_PROTECTION; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); - rc = agent_genkey (ctrl, cache_nonce, opt_timestamp, - (char*)value, valuelen, no_protection, - newpasswd, opt_preset, &outbuf); + + rc = agent_genkey (ctrl, flags, cache_nonce, opt_timestamp, + (char*)value, valuelen, + newpasswd, &outbuf); leave: if (newpasswd) @@ -1283,7 +1360,7 @@ cmd_keyattr (assuan_context_t ctx, char *line) if (!err) err = nvc_set_private_key (keymeta, s_key); if (!err) - err = agent_update_private_key (grip, keymeta); + err = agent_update_private_key (ctrl, grip, keymeta); } nvc_release (keymeta); @@ -1293,6 +1370,8 @@ cmd_keyattr (assuan_context_t ctx, char *line) leave: return leave_cmd (ctx, err); } + + static const char hlp_readkey[] = "READKEY [--no-data] [--format=ssh] \n" @@ -1356,10 +1435,17 @@ cmd_readkey (assuan_context_t ctx, char *line) goto leave; } - if (agent_key_available (grip)) + if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip)) { /* (Shadow)-key is not available in our key storage. */ - rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0); + char *dispserialno; + char hexgrip[40+1]; + + bin2hex (grip, 20, hexgrip); + agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip); + rc = agent_write_shadow_key (ctrl, grip, serialno, keyid, pkbuf, 0, + dispserialno); + xfree (dispserialno); if (rc) goto leave; } @@ -1374,7 +1460,9 @@ cmd_readkey (assuan_context_t ctx, char *line) goto leave; rc = agent_public_key_from_file (ctrl, grip, &s_pkey); - if (!rc) + if (rc) + goto leave; + else { if (opt_format_ssh) { @@ -1944,9 +2032,6 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) struct pin_entry_info_s *pi2 = NULL; int is_generated; - 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"); @@ -1995,7 +2080,9 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) if (!desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); - if (!strcmp (cacheid, "X")) + /* The only limitation in restricted mode is that we don't consider + * the cache. */ + if (ctrl->restricted || !strcmp (cacheid, "X")) cacheid = NULL; if (!strcmp (errtext, "X")) errtext = NULL; @@ -2077,7 +2164,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) entry_errtext = NULL; is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED); - /* We don't allow an empty passpharse in this mode. */ + /* We don't allow an empty passphrase in this mode. */ if (!is_generated && check_passphrase_constraints (ctrl, pi->pin, pi->constraints_flags, @@ -2289,27 +2376,31 @@ cmd_get_confirmation (assuan_context_t ctx, char *line) static const char hlp_learn[] = - "LEARN [--send] [--sendinfo] [--force]\n" + "LEARN [--send] [--sendinfo] [--force] [SERIALNO]\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."; + "private key storage will be updated by the result. With SERIALNO\n" + "given the current card is first switched to the specified one."; 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; + const char *demand_sn; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); + line = skip_options (line); + demand_sn = *line? line : NULL; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); - err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); + err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force, demand_sn); return leave_cmd (ctx, err); } @@ -2893,7 +2984,7 @@ cmd_import_key (assuan_context_t ctx, char *line) } else { - if (!force && !agent_key_available (grip)) + if (!force && !agent_key_available (ctrl, grip)) err = gpg_error (GPG_ERR_EEXIST); else { @@ -2915,12 +3006,12 @@ cmd_import_key (assuan_context_t ctx, char *line) err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count); if (!err) - err = agent_write_private_key (grip, finalkey, finalkeylen, force, - NULL, NULL, opt_timestamp); + err = agent_write_private_key (ctrl, grip, finalkey, finalkeylen, force, + NULL, NULL, NULL, opt_timestamp); } else - err = agent_write_private_key (grip, key, realkeylen, force, NULL, NULL, - opt_timestamp); + err = agent_write_private_key (ctrl, grip, key, realkeylen, force, + NULL, NULL, NULL, opt_timestamp); leave: gcry_sexp_release (openpgp_sexp); @@ -2938,7 +3029,8 @@ cmd_import_key (assuan_context_t ctx, char *line) static const char hlp_export_key[] = - "EXPORT_KEY [--cache-nonce=] [--openpgp|--mode1003] \n" + "EXPORT_KEY [--cache-nonce=] \\\n" + " [--openpgp|--mode1003] \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" @@ -2946,8 +3038,8 @@ static const char hlp_export_key[] = "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. If --mode1003 is use the secret key\n" - "is exported as s-expression as storred locally. Without those options,\n" + "compatible passphrase-protected form. In --mode1003 the secret key\n" + "is exported as s-expression as stored locally. Without those options,\n" "the secret key material will be exported in the clear (after prompting\n" "the user to unlock it, if needed).\n"; static gpg_error_t @@ -3004,7 +3096,7 @@ cmd_export_key (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; @@ -3205,6 +3297,12 @@ cmd_keytocard (assuan_context_t ctx, char *line) force = has_option (line, "--force"); line = skip_options (line); + /* Need a copy of LINE, since it might inquire to the frontend which + resulted original buffer overwritten. */ + line = xtrystrdup (line); + if (!line) + return gpg_error_from_syserror (); + argc = split_fields (line, argv, DIM (argv)); if (argc < 3) { @@ -3216,9 +3314,9 @@ cmd_keytocard (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { - err =gpg_error (GPG_ERR_NO_SECKEY); + err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } @@ -3324,6 +3422,7 @@ cmd_keytocard (assuan_context_t ctx, char *line) xfree (keydata); leave: + xfree (line); xfree (ecdh_params); gcry_sexp_release (s_skey); xfree (shadow_info); @@ -3536,7 +3635,7 @@ cmd_keytotpm (assuan_context_t ctx, char *line) if (err) goto leave; - if (agent_key_available (grip)) + if (agent_key_available (ctrl, grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; @@ -3828,6 +3927,7 @@ static const char hlp_getinfo[] = " 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" + " ephemeral - Returns OK if the connection is in ephemeral mode.\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"; @@ -3881,6 +3981,10 @@ cmd_getinfo (assuan_context_t ctx, char *line) snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } + else if (!strcmp (line, "ephemeral")) + { + rc = ctrl->ephemeral_mode? 0 : gpg_error (GPG_ERR_FALSE); + } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE); @@ -4037,6 +4141,10 @@ option_handler (assuan_context_t ctx, const char *key, const char *value) ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } + else if (!strcmp (key, "ephemeral")) + { + ctrl->ephemeral_mode = *value? atoi (value) : 0; + } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); diff --git a/agent/cvt-openpgp.c b/agent/cvt-openpgp.c index 9bb815ff8..420dbb464 100644 --- a/agent/cvt-openpgp.c +++ b/agent/cvt-openpgp.c @@ -969,7 +969,7 @@ convert_from_openpgp_main (ctrl_t ctrl, gcry_sexp_t s_pgp, int dontcare_exist, if (err) goto leave; - if (!dontcare_exist && !from_native && !agent_key_available (grip)) + if (!dontcare_exist && !from_native && !agent_key_available (ctrl, grip)) { err = gpg_error (GPG_ERR_EEXIST); goto leave; @@ -1147,17 +1147,19 @@ convert_from_openpgp_native (ctrl_t ctrl, if (!agent_protect (*r_key, passphrase, &protectedkey, &protectedkeylen, ctrl->s2k_count)) - agent_write_private_key (grip, protectedkey, protectedkeylen, 1, - NULL, NULL, 0); + agent_write_private_key (ctrl, grip, + protectedkey, + protectedkeylen, + 1, NULL, NULL, NULL, 0); xfree (protectedkey); } else { /* Empty passphrase: write key without protection. */ - agent_write_private_key (grip, + agent_write_private_key (ctrl, grip, *r_key, gcry_sexp_canon_len (*r_key, 0, NULL,NULL), - 1, NULL, NULL, 0); + 1, NULL, NULL, NULL, 0); } } @@ -1382,6 +1384,17 @@ extract_private_key (gcry_sexp_t s_key, int req_private_key_data, err = gcry_sexp_extract_param (list, NULL, format, array+0, array+1, NULL); } + else if ( !strcmp (name, (algoname = "kyber512")) + || !strcmp (name, (algoname = "kyber768")) + || !strcmp (name, (algoname = "kyber1024"))) + { + format = "/ps?"; + elems = "ps?"; + npkey = 1; + nskey = 2; + err = gcry_sexp_extract_param (list, NULL, format, + array+0, array+1, NULL); + } else { err = gpg_error (GPG_ERR_PUBKEY_ALGO); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index ed0173ea1..1e5de4671 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -90,7 +90,7 @@ has_percent0A_suffix (const char *string) INFO gets displayed as part of a generic string. However if the first character of INFO is a vertical bar all up to the next - verical bar are considered flags and only everything after the + vertical bar are considered flags and only everything after the second vertical bar gets displayed as the full prompt. Flags: @@ -377,10 +377,10 @@ divert_pksign (ctrl_t ctrl, const unsigned char *grip, } -/* Decrypt the value given asn an S-expression in CIPHER using the +/* Decrypt the value given as an s-expression in CIPHER using the key identified by SHADOW_INFO and return the plaintext in an allocated buffer in R_BUF. The padding information is stored at - R_PADDING with -1 for not known. */ + R_PADDING with -1 for not known, when it's not NULL. */ int divert_pkdecrypt (ctrl_t ctrl, const unsigned char *grip, @@ -399,7 +399,8 @@ divert_pkdecrypt (ctrl_t ctrl, bin2hex (grip, 20, hexgrip); - *r_padding = -1; + if (r_padding) + *r_padding = -1; s = cipher; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); @@ -467,7 +468,20 @@ divert_pkdecrypt (ctrl_t ctrl, n = snext (&s); } else - return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + { + if (opt.verbose) + { + if (smatch (&s, n, "elg")) + log_info ("unknown algorithm is \"elg\"\n"); + else if (smatch (&s, n, "dsa")) + log_info ("unknown algorithm is \"dsa\"\n"); + else if (smatch (&s, n, "kyber")) + log_info ("unknown algorithm is \"kyber\"\n"); + else + log_printhex (s, n, "unknown algorithm is"); + } + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } if (!n) return gpg_error (GPG_ERR_UNKNOWN_SEXP); @@ -485,6 +499,38 @@ divert_pkdecrypt (ctrl_t ctrl, return rc; } +gpg_error_t +agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh) +{ + gpg_error_t err = 0; + char *ecdh = NULL; + size_t len; + int rc; + char hexgrip[KEYGRIP_LEN*2+1]; + + bin2hex (ctrl->keygrip, KEYGRIP_LEN, hexgrip); + rc = agent_card_pkdecrypt (ctrl, hexgrip, getpin_cb, ctrl, NULL, + ecc_ct, ecc_point_len, &ecdh, &len, NULL); + if (rc) + return rc; + + if (len == ecc_point_len) + memcpy (ecc_ecdh, ecdh, len); + else if (len == ecc_point_len + 1 && ecdh[0] == 0x40) /* The prefix */ + memcpy (ecc_ecdh, ecdh + 1, len - 1); + else + { + if (opt.verbose) + log_info ("%s: ECC result length invalid (%zu != %zu)\n", + __func__, len, ecc_point_len); + return gpg_error (GPG_ERR_INV_DATA); + } + + xfree (ecdh); + return err; +} + gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno, diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c index 4cae66218..b9e8784bd 100644 --- a/agent/divert-tpm2.c +++ b/agent/divert-tpm2.c @@ -26,9 +26,10 @@ divert_tpm2_pksign (ctrl_t ctrl, static gpg_error_t agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, - unsigned char *shadow_info) + unsigned char *shadow_info, + gcry_sexp_t s_key) { - gpg_error_t err; + gpg_error_t err, err1; unsigned char *shdkey; unsigned char *pkbuf; size_t len; @@ -44,16 +45,39 @@ agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, xfree (pkbuf); if (err) { - log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); + log_error ("shadowing the tpm key failed: %s\n", gpg_strerror (err)); + return err; + } + + err = agent_delete_key (ctrl, NULL, grip, 1, 0); + if (err) + { + log_error ("failed to delete unshadowed key: %s\n", gpg_strerror (err)); + xfree (shdkey); return err; } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); - err = agent_write_private_key (grip, shdkey, len, 1 /*force*/, - NULL, NULL, 0); + err = agent_write_private_key (ctrl, grip, shdkey, len, 1 /*force*/, + NULL, NULL, NULL, 0); xfree (shdkey); if (err) - log_error ("error writing key: %s\n", gpg_strerror (err)); + { + log_error ("error writing tpm key: %s\n", gpg_strerror (err)); + + len = gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, NULL, 0); + pkbuf = xtrymalloc(len); + if (!pkbuf) + return GPG_ERR_ENOMEM; + + gcry_sexp_sprint(s_key, GCRYSEXP_FMT_CANON, pkbuf, len); + err1 = agent_write_private_key (ctrl, grip, pkbuf, len, 1 /*force*/, + NULL, NULL, NULL, 0); + xfree(pkbuf); + if (err1) + log_error ("error trying to restore private key: %s\n", + gpg_strerror (err1)); + } return err; } @@ -68,7 +92,7 @@ divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey); if (!ret) { - ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info); + ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info, s_skey); xfree (shadow_info); } return ret; @@ -83,7 +107,8 @@ divert_tpm2_pkdecrypt (ctrl_t ctrl, const unsigned char *s; size_t n; - *r_padding = -1; + if (r_padding) + *r_padding = -1; s = cipher; if (*s != '(') @@ -102,7 +127,8 @@ divert_tpm2_pkdecrypt (ctrl_t ctrl, return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "rsa")) { - *r_padding = 0; + if (r_padding) + *r_padding = 0; if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); s++; @@ -142,3 +168,34 @@ divert_tpm2_pkdecrypt (ctrl_t ctrl, return agent_tpm2d_pkdecrypt (ctrl, s, n, shadow_info, r_buf, r_len); } + +int +agent_tpm2d_ecc_kem (ctrl_t ctrl, + const unsigned char *shadow_info, + const unsigned char *ecc_ct, + size_t ecc_point_len, unsigned char *ecc_ecdh) +{ + char *ecdh = NULL; + size_t len; + int rc; + + rc = agent_tpm2d_pkdecrypt (ctrl, ecc_ct, ecc_point_len, shadow_info, + &ecdh, &len); + if (rc) + return rc; + + if (len == ecc_point_len) + memcpy (ecc_ecdh, ecdh, len); + else if (len == ecc_point_len + 1 && ecdh[0] == 0x40) /* The prefix */ + memcpy (ecc_ecdh, ecdh + 1, len - 1); + else + { + if (opt.verbose) + log_info ("%s: ECC result length invalid (%zu != %zu)\n", + __func__, len, ecc_point_len); + return gpg_error (GPG_ERR_INV_DATA); + } + + xfree (ecdh); + return rc; +} diff --git a/agent/findkey.c b/agent/findkey.c index 098d5224f..5ffed6ae1 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -39,6 +39,13 @@ #define O_BINARY 0 #endif + +static gpg_error_t read_key_file (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t *result, nvc_t *r_keymeta, + char **r_orig_key_value); +static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey); + + /* Helper to pass data to the check callback of the unprotect function. */ struct try_unprotect_arg_s { @@ -50,6 +57,46 @@ struct try_unprotect_arg_s }; +/* Return the file name for the 20 byte keygrip GRIP. With FOR_NEW + * create a file name for later renaming to the actual name. Return + * NULL on error. */ +static char * +fname_from_keygrip (const unsigned char *grip, int for_new) +{ + char hexgrip[40+4+4+1]; + + bin2hex (grip, 20, hexgrip); + strcpy (hexgrip+40, for_new? ".key.tmp" : ".key"); + + return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, + hexgrip, NULL); +} + + +/* Helper until we have a "wipe" mode flag in es_fopen. */ +static void +wipe_and_fclose (estream_t fp) +{ + void *blob; + size_t blob_size; + + if (!fp) + ; + else if (es_fclose_snatch (fp, &blob, &blob_size)) + { + log_error ("error wiping buffer during fclose\n"); + es_fclose (fp); + } + else if (blob) + { + wipememory (blob, blob_size); + gpgrt_free (blob); + } +} + + + + /* Replace all linefeeds in STRING by "%0A" and return a new malloced * string. May return NULL on memory error. */ static char * @@ -84,135 +131,73 @@ linefeed_to_percent0A (const char *string) * storage. With FORCE passed as true an existing key with the given * GRIP will get overwritten. If SERIALNO and KEYREF are given a * Token line is added to the key if the extended format is used. If - * TIMESTAMP is not zero and the key doies not yet exists it will be + * TIMESTAMP is not zero and the key does not yet exists it will be * recorded as creation date. */ -int -agent_write_private_key (const unsigned char *grip, +gpg_error_t +agent_write_private_key (ctrl_t ctrl, + const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, + const char *dispserialno, time_t timestamp) { gpg_error_t err; - char *fname; - estream_t fp; - char hexgrip[40+4+1]; - int update, newkey; + char *fname = NULL; + char *tmpfname = NULL; + estream_t fp = NULL; + int newkey = 0; nvc_t pk = NULL; gcry_sexp_t key = NULL; - int remove = 0; + int removetmp = 0; + char *token0 = NULL; char *token = NULL; + char *dispserialno_buffer = NULL; + char **tokenfields = NULL; + int is_regular; + int blocksigs = 0; + char *orig_key_value = NULL; + const char *s; + int force_modify = 0; - bin2hex (grip, 20, hexgrip); - strcpy (hexgrip+40, ".key"); + fname = (ctrl->ephemeral_mode + ? xtrystrdup ("[ephemeral key store]") + : fname_from_keygrip (grip, 0)); + if (!fname) + return gpg_error_from_syserror (); - 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 && !gnupg_access (fname, F_OK)) + err = read_key_file (ctrl, grip, &key, &pk, &orig_key_value); + if (err) { - 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; - } - update = 0; - newkey = 1; - } - 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 still in the old format - force it into extended - * format. We do not request an update here because an - * existing key is not yet in extended key format and no - * extended infos are yet available. */ - update = 0; - newkey = 0; - } + if (gpg_err_code (err) == GPG_ERR_ENOENT) + newkey = 1; else { - /* Key is already in the extended format. */ - update = 1; - newkey = 0; - } - } - else - { - /* The key file did not exist: we assume this is a new key and - * write the Created: entry. */ - update = 0; - newkey = 1; - } - - - 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)); + log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } - else + + nvc_modified (pk, 1); /* Clear that flag after a read. */ + + if (!pk) { + /* Key is still in the old format or does not exist - create a + * new container. */ pk = nvc_new_private_key (); if (!pk) { err = gpg_error_from_syserror (); goto leave; } + force_modify = 1; } - es_clearerr (fp); + + /* Check whether we already have a regular key. */ + is_regular = (key && gpg_err_code (is_shadowed_key (key)) != GPG_ERR_TRUE); /* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into * our name value container. */ + gcry_sexp_release (key); err = gcry_sexp_sscan (&key, NULL, buffer, length); if (err) goto leave; @@ -220,24 +205,68 @@ agent_write_private_key (const unsigned char *grip, if (err) goto leave; + /* Detect whether the key value actually changed and if not clear + * the modified flag. This extra check is required because + * read_key_file removes the Key entry from the container and we + * then create a new Key entry which might be the same, though. */ + if (!force_modify + && orig_key_value && (s = nvc_get_string (pk, "Key:")) + && !strcmp (orig_key_value, s)) + { + nvc_modified (pk, 1); /* Clear that flag. */ + } + xfree (orig_key_value); + orig_key_value = NULL; + + /* Check that we do not update a regular key with a shadow key. */ + if (is_regular && gpg_err_code (is_shadowed_key (key)) == GPG_ERR_TRUE) + { + log_info ("updating regular key file '%s'" + " by a shadow key inhibited\n", fname); + err = 0; /* Simply ignore the error. */ + goto leave; + } + + /* Check that we update a regular key only in force mode. */ + if (is_regular && !force) + { + log_error ("secret key file '%s' already exists\n", fname); + err = gpg_error (GPG_ERR_EEXIST); + goto leave; + } + /* If requested write a Token line. */ if (serialno && keyref) { nve_t item; - const char *s; + size_t token0len; - token = strconcat (serialno, " ", keyref, NULL); - if (!token) + if (dispserialno) + { + /* Escape the DISPSERIALNO. */ + dispserialno_buffer = percent_plus_escape (dispserialno); + if (!dispserialno_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + dispserialno = dispserialno_buffer; + } + + token0 = strconcat (serialno, " ", keyref, NULL); + if (token0) + token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL); + if (!token0 || !token) { err = gpg_error_from_syserror (); goto leave; } - /* fixme: the strcmp should compare only the first two strings. */ + token0len = strlen (token0); for (item = nvc_lookup (pk, "Token:"); item; item = nve_next_value (item, "Token:")) - if ((s = nve_value (item)) && !strcmp (s, token)) + if ((s = nve_value (item)) && !strncmp (s, token0, token0len)) break; if (!item) { @@ -248,6 +277,23 @@ agent_write_private_key (const unsigned char *grip, if (err) goto leave; } + else + { + /* Token exists: Update the display s/n. It may have + * changed due to changes in a newer software version. */ + if (s && (tokenfields = strtokenize (s, " \t\n")) + && tokenfields[0] && tokenfields[1] && tokenfields[2] + && tokenfields[3] + && !strcmp (tokenfields[3], dispserialno)) + ; /* No need to update Token entry. */ + else + { + err = nve_set (pk, item, token); + if (err) + goto leave; + } + } + } /* If a timestamp has been supplied and the key is new, write a @@ -263,103 +309,246 @@ agent_write_private_key (const unsigned char *grip, goto leave; } - /* Back to start and write. */ - err = es_fseek (fp, 0, SEEK_SET); - if (err) - goto leave; - - err = nvc_write (pk, fp); - if (!err) - err = es_fflush (fp); - if (err) + /* Check whether we need to write the file at all. */ + if (!nvc_modified (pk, 0)) { - log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); - remove = 1; + err = 0; goto leave; } - if (ftruncate (es_fileno (fp), es_ftello (fp))) + if (ctrl->ephemeral_mode) { - err = gpg_error_from_syserror (); - log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); - remove = 1; - goto leave; - } + ephemeral_private_key_t ek; + void *blob; + size_t blobsize; - if (es_fclose (fp)) - { - err = gpg_error_from_syserror (); - log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); - remove = 1; - goto leave; + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + ek = xtrycalloc (1, sizeof *ek); + if (!ek) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (ek->grip, grip, KEYGRIP_LEN); + ek->next = ctrl->ephemeral_keys; + ctrl->ephemeral_keys = ek; + } + + fp = es_fopenmem (0, "wb,wipe"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open memory stream: %s\n", gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (err) + { + log_error ("error writing to memory stream: %s\n",gpg_strerror (err)); + goto leave; + } + + if (es_fclose_snatch (fp, &blob, &blobsize) || !blob) + { + err = gpg_error_from_syserror (); + log_error ("error getting memory stream buffer: %s\n", + gpg_strerror (err)); + /* Closing right away so that we don't try another snatch in + * the cleanup. */ + es_fclose (fp); + fp = NULL; + goto leave; + } + fp = NULL; + xfree (ek->keybuf); + ek->keybuf = blob; + ek->keybuflen = blobsize; } else - fp = NULL; + { + /* Create a temporary file for writing. */ + tmpfname = fname_from_keygrip (grip, 1); + fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL; + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (!err && es_fflush (fp)) + err = gpg_error_from_syserror (); + if (err) + { + log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + + if (es_fclose (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + else + fp = NULL; + + err = gnupg_rename_file (tmpfname, fname, &blocksigs); + if (err) + { + err = gpg_error_from_syserror (); + log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + } bump_key_eventcounter (); leave: - es_fclose (fp); - if (remove) - gnupg_remove (fname); + if (blocksigs) + gnupg_unblock_all_signals (); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); + if (removetmp && tmpfname) + gnupg_remove (tmpfname); + xfree (orig_key_value); xfree (fname); + xfree (tmpfname); + xfree (token); + xfree (token0); + xfree (dispserialno_buffer); + xfree (tokenfields); gcry_sexp_release (key); nvc_release (pk); - xfree (token); return err; } gpg_error_t -agent_update_private_key (const unsigned char *grip, nvc_t pk) +agent_update_private_key (ctrl_t ctrl, const unsigned char *grip, nvc_t pk) { - char *fname, *fname0; - estream_t fp; - char hexgrip[40+8+1]; gpg_error_t err; + char *fname0 = NULL; /* The existing file name. */ + char *fname = NULL; /* The temporary new file name. */ + estream_t fp = NULL; + int removetmp = 0; + int blocksigs = 0; - bin2hex (grip, 20, hexgrip); - strcpy (hexgrip+40, ".key.tmp"); + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + void *blob; + size_t blobsize; - fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, - hexgrip, NULL); - fname0 = xstrdup (fname); + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + + fp = es_fopenmem (0, "wbx,wipe"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't open memory stream: %s\n", gpg_strerror (err)); + goto leave; + } + + err = nvc_write (pk, fp); + if (err) + { + log_error ("error writing to memory stream: %s\n",gpg_strerror (err)); + goto leave; + } + + if (es_fclose_snatch (fp, &blob, &blobsize) || !blob) + { + err = gpg_error_from_syserror (); + log_error ("error getting memory stream buffer: %s\n", + gpg_strerror (err)); + /* Closing right away so that we don't try another snatch in + * the cleanup. */ + es_fclose (fp); + fp = NULL; + goto leave; + } + fp = NULL; + /* No need to revisit the linked list because the found EK is + * not expected to change due to the other syscalls above. */ + xfree (ek->keybuf); + ek->keybuf = blob; + ek->keybuflen = blobsize; + goto leave; + } + + + fname0 = fname_from_keygrip (grip, 0); if (!fname0) { err = gpg_error_from_syserror (); - xfree (fname); - return err; + goto leave; + } + fname = fname_from_keygrip (grip, 1); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; } - fname0[strlen (fname)-4] = 0; fp = es_fopen (fname, "wbx,mode=-rw"); if (!fp) { err = gpg_error_from_syserror (); - log_error ("can't create '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - return err; + goto leave; } err = nvc_write (pk, fp); if (err) - log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); - - es_fclose (fp); - -#ifdef HAVE_W32_SYSTEM - /* No atomic mv on W32 systems. */ - gnupg_remove (fname0); -#endif - if (rename (fname, fname0)) { - err = gpg_error_from_errno (errno); - log_error (_("error renaming '%s' to '%s': %s\n"), - fname, fname0, strerror (errno)); + log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); + removetmp = 1; + goto leave; } + es_fclose (fp); + fp = NULL; + + err = gnupg_rename_file (fname, fname0, &blocksigs); + if (err) + { + err = gpg_error_from_syserror (); + log_error ("error renaming '%s': %s\n", fname, gpg_strerror (err)); + removetmp = 1; + goto leave; + } + + + leave: + if (blocksigs) + gnupg_unblock_all_signals (); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); + if (removetmp && fname) + gnupg_remove (fname); xfree (fname); + xfree (fname0); return err; } @@ -661,6 +850,8 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, *keybuf = result; return 0; } + else if (opt.verbose) + log_info (_("Unprotecting key failed: %s\n"), gpg_strerror (rc)); xfree (pw); } } @@ -832,40 +1023,67 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, /* 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 + * 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. */ + * it. If R_ORIG_KEY_VALUE is non-NULL and the Key item was removed, + * its original value is stored at that R_ORIG_KEY_VALUE and the + * caller must free 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) +read_key_file (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value) { gpg_error_t err; char *fname; - estream_t fp; - struct stat st; - unsigned char *buf; + estream_t fp = NULL; + unsigned char *buf = NULL; size_t buflen, erroff; - gcry_sexp_t s_skey; - char hexgrip[40+4+1]; + nvc_t pk = NULL; char first; + size_t keybuflen; *result = NULL; if (r_keymeta) *r_keymeta = NULL; + if (r_orig_key_value) + *r_orig_key_value = NULL; - bin2hex (grip, 20, hexgrip); - strcpy (hexgrip+40, ".key"); + fname = (ctrl->ephemeral_mode + ? xtrystrdup ("[ephemeral key store]") + : fname_from_keygrip (grip, 0)); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } - fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, - hexgrip, NULL); - fp = es_fopen (fname, "rb"); + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN) + && ek->keybuf && ek->keybuflen) + break; + if (!ek || !ek->keybuf || !ek->keybuflen) + { + err = gpg_error (GPG_ERR_ENOENT); + goto leave; + } + keybuflen = ek->keybuflen; + fp = es_fopenmem_init (0, "rb", ek->keybuf, ek->keybuflen); + } + else + { + keybuflen = 0; /* Indicates that this is not ephemeral mode. */ + 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; + goto leave; } if (es_fread (&first, 1, 1, fp) != 1) @@ -873,28 +1091,22 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta) 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; + goto leave; } 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; + goto leave; } 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", @@ -906,38 +1118,49 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta) log_error ("error getting private key from '%s': %s\n", fname, gpg_strerror (err)); else - nvc_delete_named (pk, "Key:"); + { + if (r_orig_key_value) + { + const char *s = nvc_get_string (pk, "Key:"); + if (s) + { + *r_orig_key_value = xtrystrdup (s); + if (!*r_orig_key_value) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + } + nvc_delete_named (pk, "Key:"); + } } - if (!err && r_keymeta) - *r_keymeta = pk; - else - nvc_release (pk); - xfree (fname); - return err; + goto leave; /* Ready. */ } - if (fstat (es_fileno (fp), &st)) + if (keybuflen) + buflen = keybuflen; + else { - err = gpg_error_from_syserror (); - log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); - xfree (fname); - es_fclose (fp); - return err; + struct stat st; + + if (fstat (es_fileno (fp), &st)) + { + err = gpg_error_from_syserror (); + log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + buflen = st.st_size; } - 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; - + goto leave; } if (es_fread (buf, buflen, 1, fp) != 1) @@ -945,25 +1168,33 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta) 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; + goto leave; } /* 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) - { + { + gcry_sexp_t s_skey; + + err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); + 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; + else + *result = s_skey; + } + + leave: + xfree (buf); + if (!err && r_keymeta) + *r_keymeta = pk; + else + nvc_release (pk); + if (ctrl->ephemeral_mode) + wipe_and_fclose (fp); + else + es_fclose (fp); + xfree (fname); + return err; } @@ -973,12 +1204,12 @@ 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); + fname = fname_from_keygrip (grip, 0); + if (!fname) + { + return gpg_error_from_syserror (); + } if (gnupg_remove (fname)) err = gpg_error_from_syserror (); xfree (fname); @@ -1114,22 +1345,23 @@ prompt_for_card (ctrl_t ctrl, const unsigned char *grip, /* Return the secret key as an S-Exp in RESULT after locating it using the GRIP. Caller should set GRIP=NULL, when a key in a file is - intended to be used for cryptographic operation. In this case, - CTRL->keygrip is used to locate the file, and it may ask a user for - confirmation. 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. */ + intended to be used for cryptographic operation (except the case of + GRIP==CTRL->keygrip1 for PQC). When GRIP==NULL, CTRL->keygrip is + used to locate the file. When GRIP==NULL or GRIP==CTRL->keygrip1, + it may ask a user for confirmation. 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, @@ -1144,6 +1376,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, gcry_sexp_t s_skey; nvc_t keymeta = NULL; char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */ + int for_crypto_operation = 0; *result = NULL; if (shadow_info) @@ -1153,10 +1386,18 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, if (r_timestamp) *r_timestamp = (time_t)(-1); - if (!grip && !ctrl->have_keygrip) - return gpg_error (GPG_ERR_NO_SECKEY); + if (!grip) + { + if (!ctrl->have_keygrip) + return gpg_error (GPG_ERR_NO_SECKEY); + for_crypto_operation = 1; + grip = ctrl->keygrip; + } + else if (grip == ctrl->keygrip1) + /* This is the use case for composite PQC, */ + for_crypto_operation = 1; - err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta); + err = read_key_file (ctrl, grip, &s_skey, &keymeta, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -1186,7 +1427,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, *r_timestamp = isotime2epoch (created); } - if (!grip && keymeta) + if (for_crypto_operation && keymeta) { const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:"); @@ -1288,8 +1529,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, if (!err) { - err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, - grip? grip : ctrl->keygrip, + 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", @@ -1321,12 +1561,12 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, { memcpy (*shadow_info, s, n); /* - * When it's a key on card (not on tpm2), maks sure + * When it's a key on card (not on tpm2), make sure * it's available. */ - if (strcmp (shadow_type, "t1-v1") == 0 && !grip) - err = prompt_for_card (ctrl, ctrl->keygrip, - keymeta, *shadow_info); + if (strcmp (shadow_type, "t1-v1") == 0 + && for_crypto_operation) + err = prompt_for_card (ctrl, grip, keymeta, *shadow_info); } } else @@ -1377,6 +1617,29 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, } +/* This function returns GPG_ERR_TRUE if S_SKEY represents a shadowed + * key. 0 is return for other key types. Any other error may occur + * if S_SKEY is not valid. */ +static gpg_error_t +is_shadowed_key (gcry_sexp_t s_skey) +{ + gpg_error_t err; + unsigned char *buf; + size_t buflen; + + err = make_canon_sexp (s_skey, &buf, &buflen); + if (err) + return err; + + if (agent_private_key_type (buf) == PRIVATE_KEY_SHADOWED) + err = gpg_error (GPG_ERR_TRUE); + + wipememory (buf, buflen); + xfree (buf); + return err; +} + + /* 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 @@ -1392,7 +1655,7 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, *result = NULL; - err = read_key_file (grip, &s_skey, r_keymeta); + err = read_key_file (ctrl, grip, &s_skey, r_keymeta, NULL); if (!err) *result = s_skey; return err; @@ -1435,7 +1698,7 @@ public_key_from_file (ctrl_t ctrl, const unsigned char *grip, if (r_sshorder) *r_sshorder = 0; - err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL); + err = read_key_file (ctrl, grip, &s_skey, for_ssh? &keymeta : NULL, NULL); if (err) return err; @@ -1563,13 +1826,23 @@ agent_ssh_key_from_file (ctrl_t ctrl, /* Check whether the secret key identified by GRIP is available. - Returns 0 is the key is available. */ + Returns 0 is the key is available. */ int -agent_key_available (const unsigned char *grip) +agent_key_available (ctrl_t ctrl, const unsigned char *grip) { int result; char *fname; char hexgrip[40+4+1]; + ephemeral_private_key_t ek; + + if (ctrl && ctrl->ephemeral_mode) + { + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN) + && ek->keybuf && ek->keybuflen) + return 0; + return -1; + } bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); @@ -1582,7 +1855,6 @@ agent_key_available (const unsigned char *grip) } - /* 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 @@ -1607,7 +1879,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, { gcry_sexp_t sexp; - err = read_key_file (grip, &sexp, NULL); + err = read_key_file (ctrl, grip, &sexp, NULL, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) @@ -1691,7 +1963,13 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text, char *default_desc = NULL; int key_type; - err = read_key_file (grip, &s_skey, NULL); + if (ctrl->ephemeral_mode) + { + err = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + err = read_key_file (ctrl, grip, &s_skey, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); if (err) @@ -1792,9 +2070,10 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text, 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, +agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip, const char *serialno, const char *keyid, - const unsigned char *pkbuf, int force) + const unsigned char *pkbuf, int force, + const char *dispserialno) { gpg_error_t err; unsigned char *shadow_info; @@ -1821,7 +2100,8 @@ agent_write_shadow_key (const unsigned char *grip, } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); - err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid, 0); + err = agent_write_private_key (ctrl, grip, shdkey, len, force, + serialno, keyid, dispserialno, 0); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); diff --git a/agent/genkey.c b/agent/genkey.c index 7660443ca..0fb94350d 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -27,17 +27,38 @@ #include "agent.h" #include "../common/i18n.h" -#include "../common/exechelp.h" #include "../common/sysutils.h" -static int -store_key (gcry_sexp_t private, const char *passphrase, int force, + +void +clear_ephemeral_keys (ctrl_t ctrl) +{ + while (ctrl->ephemeral_keys) + { + ephemeral_private_key_t next = ctrl->ephemeral_keys->next; + if (ctrl->ephemeral_keys->keybuf) + { + wipememory (ctrl->ephemeral_keys->keybuf, + ctrl->ephemeral_keys->keybuflen); + xfree (ctrl->ephemeral_keys->keybuf); + } + xfree (ctrl->ephemeral_keys); + ctrl->ephemeral_keys = next; + } +} + + +/* Store the key either to a file, or in ctrl->ephemeral_mode in the + * session data. */ +static gpg_error_t +store_key (ctrl_t ctrl, gcry_sexp_t private, + const char *passphrase, int force, unsigned long s2k_count, time_t timestamp) { - int rc; + gpg_error_t err; unsigned char *buf; size_t len; - unsigned char grip[20]; + unsigned char grip[KEYGRIP_LEN]; if ( !gcry_pk_get_keygrip (private, grip) ) { @@ -49,7 +70,10 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, log_assert (len); buf = gcry_malloc_secure (len); if (!buf) - return out_of_core (); + { + err = gpg_error_from_syserror (); + goto leave; + } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); log_assert (len); @@ -57,19 +81,57 @@ store_key (gcry_sexp_t private, const char *passphrase, int force, { unsigned char *p; - rc = agent_protect (buf, passphrase, &p, &len, s2k_count); - if (rc) - { - xfree (buf); - return rc; - } + err = agent_protect (buf, passphrase, &p, &len, s2k_count); + if (err) + goto leave; xfree (buf); buf = p; } - rc = agent_write_private_key (grip, buf, len, force, NULL, NULL, timestamp); + if (ctrl->ephemeral_mode) + { + ephemeral_private_key_t ek; + + for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) + if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) + break; + if (!ek) + { + ek = xtrycalloc (1, sizeof *ek); + if (!ek) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (ek->grip, grip, KEYGRIP_LEN); + ek->next = ctrl->ephemeral_keys; + ctrl->ephemeral_keys = ek; + } + if (ek->keybuf) + { + wipememory (ek->keybuf, ek->keybuflen); + xfree (ek->keybuf); + } + ek->keybuf = buf; + buf = NULL; + ek->keybuflen = len; + err = 0; + } + else + err = agent_write_private_key (ctrl, grip, buf, len, force, + NULL, NULL, NULL, timestamp); + + if (!err) + { + char hexgrip[2*KEYGRIP_LEN+1]; + + bin2hex (grip, KEYGRIP_LEN, hexgrip); + agent_write_status (ctrl, "KEYGRIP", hexgrip, NULL); + } + + leave: xfree (buf); - return rc; + return err; } @@ -99,7 +161,7 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); estream_t stream_to_check_pattern = NULL; const char *argv[10]; - pid_t pid; + gpgrt_process_t proc; int result, i; const char *pattern; char *patternfname; @@ -142,11 +204,17 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) argv[i] = NULL; log_assert (i < sizeof argv); - if (gnupg_spawn_process (pgmname, argv, NULL, 0, - &stream_to_check_pattern, NULL, NULL, &pid)) + if (gpgrt_process_spawn (pgmname, argv, + GPGRT_PROCESS_STDIN_PIPE, + NULL, &proc)) result = 1; /* Execute error - assume password should no be used. */ else { + int status; + + gpgrt_process_get_streams (proc, 0, &stream_to_check_pattern, + NULL, NULL); + es_set_binary (stream_to_check_pattern); if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1) { @@ -157,11 +225,13 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) else es_fflush (stream_to_check_pattern); es_fclose (stream_to_check_pattern); - if (gnupg_wait_process (pgmname, pid, 1, NULL)) + gpgrt_process_wait (proc, 1); + gpgrt_process_ctl (proc, GPGRT_PROCESS_GET_EXIT_ID, &status); + if (status) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ - gnupg_release_process (pid); + gpgrt_process_release (proc); } xfree (patternfname); @@ -449,16 +519,19 @@ agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, /* Generate a new keypair according to the parameters given in - KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase - using the cache nonce. If NO_PROTECTION is true the key will not - be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that - passphrase will be used for the new key. If TIMESTAMP is not zero - it will be recorded as creation date of the key (unless extended - format is disabled) . */ + * KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase + * using the cache nonce. If NO_PROTECTION is true the key will not + * be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that + * passphrase will be used for the new key. If TIMESTAMP is not zero + * it will be recorded as creation date of the key (unless extended + * format is disabled). In ctrl_ephemeral_mode the key is stored in + * the session data and an identifier is returned using a status + * line. */ int -agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, - const char *keyparam, size_t keyparamlen, int no_protection, - const char *override_passphrase, int preset, membuf_t *outbuf) +agent_genkey (ctrl_t ctrl, unsigned int flags, + const char *cache_nonce, time_t timestamp, + const char *keyparam, size_t keyparamlen, + const char *override_passphrase, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; char *passphrase_buffer = NULL; @@ -477,7 +550,7 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, /* Get the passphrase now, cause key generation may take a while. */ if (override_passphrase) passphrase = override_passphrase; - else if (no_protection || !cache_nonce) + else if ((flags & GENKEY_FLAG_NO_PROTECTION) || !cache_nonce) passphrase = NULL; else { @@ -485,8 +558,8 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, passphrase = passphrase_buffer; } - if (passphrase || no_protection) - ; + if (passphrase || (flags & GENKEY_FLAG_NO_PROTECTION)) + ; /* No need to ask for a passphrase. */ else { rc = agent_ask_new_passphrase (ctrl, @@ -531,11 +604,14 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ - if (DBG_CRYPTO) - log_debug ("storing private key\n"); - rc = store_key (s_private, passphrase, 0, ctrl->s2k_count, timestamp); - if (!rc) + if (opt.verbose) + log_info ("storing %sprivate key\n", + ctrl->ephemeral_mode?"ephemeral ":""); + rc = store_key (ctrl, s_private, passphrase, 0, ctrl->s2k_count, timestamp); + if (!rc && !ctrl->ephemeral_mode) { + /* FIXME: or does it make sense to also cache passphrases in + * ephemeral mode using a dedicated cache? */ if (!cache_nonce) { char tmpbuf[12]; @@ -543,21 +619,23 @@ agent_genkey (ctrl_t ctrl, const char *cache_nonce, time_t timestamp, cache_nonce = bin2hex (tmpbuf, 12, NULL); } if (cache_nonce - && !no_protection + && !(flags & GENKEY_FLAG_NO_PROTECTION) && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, ctrl->cache_ttl_opt_preset)) agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL); - if (preset && !no_protection) - { - unsigned char grip[20]; - char hexgrip[40+1]; - if (gcry_pk_get_keygrip (s_private, grip)) - { - bin2hex(grip, 20, hexgrip); - rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase, + if ((flags & GENKEY_FLAG_PRESET) + && !(flags & GENKEY_FLAG_NO_PROTECTION)) + { + unsigned char grip[20]; + char hexgrip[40+1]; + if (gcry_pk_get_keygrip (s_private, grip)) + { + bin2hex(grip, 20, hexgrip); + rc = agent_put_cache (ctrl, hexgrip, + CACHE_MODE_ANY, passphrase, ctrl->cache_ttl_opt_preset); - } - } + } + } } xfree (passphrase_buffer); passphrase_buffer = NULL; @@ -606,7 +684,8 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, if (passphrase_addr && *passphrase_addr) { /* Take an empty string as request not to protect the key. */ - err = store_key (s_skey, **passphrase_addr? *passphrase_addr:NULL, 1, + err = store_key (ctrl, s_skey, + **passphrase_addr? *passphrase_addr:NULL, 1, ctrl->s2k_count, 0); } else @@ -622,7 +701,7 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, L_("Please enter the new passphrase"), &pass); if (!err) - err = store_key (s_skey, pass, 1, ctrl->s2k_count, 0); + err = store_key (ctrl, s_skey, pass, 1, ctrl->s2k_count, 0); if (!err && passphrase_addr) *passphrase_addr = pass; else diff --git a/agent/gpg-agent-w32info.rc b/agent/gpg-agent-w32info.rc index d586cad0c..a0311b2cd 100644 --- a/agent/gpg-agent-w32info.rc +++ b/agent/gpg-agent-w32info.rc @@ -48,3 +48,5 @@ VALUE "Translation", 0x409, 0x4b0 END END + +1 RT_MANIFEST "gpg-agent.w32-manifest" diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 1db422737..ae1295977 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -146,6 +146,7 @@ enum cmd_and_opt_values oAutoExpandSecmem, oListenBacklog, oInactivityTimeout, + oChangeStdEnvName, oWriteEnvFile, @@ -169,7 +170,7 @@ static gpgrt_opt_t opts[] = { 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", "@"), + ARGPARSE_s_n (oSupervised, "deprecated-supervised", "@"), #endif ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), @@ -239,7 +240,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), - + ARGPARSE_s_s (oChangeStdEnvName, "change-std-env-name", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), @@ -341,15 +342,13 @@ static struct debug_flags_s debug_flags [] = #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. - * 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. */ -#define TIMERTICK_INTERVAL (4) +/* 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. + * Values is in seconds. */ #define CHECK_OWN_SOCKET_INTERVAL (60) - +/* CHECK_PROBLEMS_INTERVAL defines how often we check the existence of + * parent process and homedir. Value is in seconds. */ +#define CHECK_PROBLEMS_INTERVAL (4) /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; @@ -366,7 +365,7 @@ static int putty_support; /* Path to the pipe, which handles requests from Win32-OpenSSH. */ static const char *win32_openssh_support; -#define W32_DEFAILT_AGENT_PIPE_NAME "\\\\.\\pipe\\openssh-ssh-agent" +#define W32_DEFAULT_AGENT_PIPE_NAME "\\\\.\\pipe\\openssh-ssh-agent" #endif /*HAVE_W32_SYSTEM*/ /* The list of open file descriptors at startup. Note that this list @@ -384,9 +383,6 @@ static int startup_signal_mask_valid; /* 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; @@ -396,6 +392,12 @@ static int is_supervised; /* Flag indicating to start the daemon even if one already runs. */ static int steal_socket; +/* Flag to monitor problems. */ +static int problem_detected; +#define AGENT_PROBLEM_SOCKET_TAKEOVER (1 << 0) +#define AGENT_PROBLEM_PARENT_HAS_GONE (1 << 1) +#define AGENT_PROBLEM_HOMEDIR_REMOVED (1 << 2) + /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; @@ -432,6 +434,17 @@ static assuan_sock_nonce_t socket_nonce_ssh; * Let's try this as default. Change at runtime with --listen-backlog. */ static int listen_backlog = 64; +#ifdef HAVE_W32_SYSTEM +/* The event to break the select call. */ +static HANDLE the_event2; +#elif defined(HAVE_PSELECT_NO_EINTR) +/* An FD to break the select call. */ +static int event_pipe_fd; +#else +/* PID of the main thread. */ +static pid_t main_thread_pid; +#endif + /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; @@ -452,9 +465,14 @@ static const char *debug_level; 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. */ +#ifdef HAVE_W32_SYSTEM +#define HAVE_PARENT_PID_SUPPORT 0 +#else +#define HAVE_PARENT_PID_SUPPORT 1 +#endif +/* The check_others_thread() 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 @@ -462,11 +480,6 @@ static pid_t parent_pid = (pid_t)(-1); * 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; @@ -516,13 +529,13 @@ 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); + gnupg_fd_t listen_fd_ssh, + int reliable_homedir_inotify); static int check_for_running_agent (int silent); - -/* Pth wrapper function definitions. */ -ASSUAN_SYSTEM_NPTH_IMPL; - +#if CHECK_OWN_SOCKET_INTERVAL > 0 +static void *check_own_socket_thread (void *arg); +#endif +static void *check_others_thread (void *arg); /* Functions. @@ -702,10 +715,10 @@ map_supervised_sockets (gnupg_fd_t *r_fd, envvar = getenv ("LISTEN_PID"); if (!envvar) log_error ("no LISTEN_PID environment variable found in " - "--supervised mode (ignoring)\n"); + "--deprecated-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", + " our pid (%lu) in --deprecated-supervised mode (ignoring)\n", (unsigned long)strtoul (envvar, NULL, 10), (unsigned long)getpid ()); @@ -735,21 +748,23 @@ map_supervised_sockets (gnupg_fd_t *r_fd, fd_count = atoi (envvar); else if (fdnames) { - log_error ("no LISTEN_FDS environment variable found in --supervised" + log_error ("no LISTEN_FDS environment variable found in" + " --deprecated-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" + "found in --deprecated-supervised mode" " (assuming 1 active descriptor)\n"); fd_count = 1; } if (fd_count < 1) { - log_error ("--supervised mode expects at least one file descriptor" + log_error ("--deprecated-supervised mode expects at least" + " one file descriptor" " (was told %d, carrying on as though it were 1)\n", fd_count); fd_count = 1; @@ -762,11 +777,12 @@ map_supervised_sockets (gnupg_fd_t *r_fd, if (fd_count != 1) log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1" - " in --supervised mode." + " in --deprecated-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" + log_fatal ("file descriptor 3 must be valid in" + " --depreacted-supervised mode" " if LISTEN_FDNAMES is not set\n"); *r_fd = 3; socket_name = gnupg_get_socket_name (3); @@ -774,7 +790,7 @@ map_supervised_sockets (gnupg_fd_t *r_fd, else if (fd_count != nfdnames) { log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match " - "LISTEN_FDS (%d) in --supervised mode\n", + "LISTEN_FDS (%d) in --deprecated-supervised mode\n", nfdnames, fd_count); } else @@ -864,6 +880,7 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.debug = 0; opt.no_grab = 1; opt.debug_pinentry = 0; + xfree (opt.pinentry_program); opt.pinentry_program = NULL; opt.pinentry_touch_file = NULL; xfree (opt.pinentry_invisible_char); @@ -924,7 +941,10 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) 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 oPinentryProgram: + xfree (opt.pinentry_program); + opt.pinentry_program = make_filename_try (pargs->r.ret_str, NULL); + break; case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; case oPinentryInvisibleChar: xfree (opt.pinentry_invisible_char); @@ -1046,6 +1066,7 @@ thread_init_once (void) * initialized and thus Libgcrypt could not set its system call * clamp. */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); + assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL); } @@ -1053,7 +1074,6 @@ static void initialize_modules (void) { thread_init_once (); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_daemon (); @@ -1081,6 +1101,7 @@ main (int argc, char **argv) int gpgconf_list = 0; gpg_error_t err; struct assuan_malloc_hooks malloc_hooks; + int reliable_homedir_inotify = 1; early_system_init (); @@ -1113,7 +1134,6 @@ main (int argc, char **argv) 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 (); @@ -1200,7 +1220,7 @@ main (int argc, char **argv) * Now we are now working under our real uid */ - /* The configuraton directories for use by gpgrt_argparser. */ + /* The configuration directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); @@ -1209,7 +1229,7 @@ main (int argc, char **argv) pargs.argc = &argc; pargs.argv = &argv; /* We are re-using the struct, thus the reset flag. We OR the - * flags so that the internal intialized flag won't be cleared. */ + * flags so that the internal initialized flag won't be cleared. */ pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS @@ -1280,6 +1300,10 @@ main (int argc, char **argv) case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; + case oChangeStdEnvName: + session_env_mod_stdenvnames (pargs.r.ret_str); + break; + case oSSHSupport: ssh_support = 1; break; @@ -1295,7 +1319,7 @@ main (int argc, char **argv) if (pargs.r_type) win32_openssh_support = pargs.r.ret_str; else - win32_openssh_support = W32_DEFAILT_AGENT_PIPE_NAME; + win32_openssh_support = W32_DEFAULT_AGENT_PIPE_NAME; # endif break; @@ -1310,10 +1334,7 @@ main (int argc, char **argv) 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*/, + gcry_control (GCRYCTL_AUTO_EXPAND_SECMEM, (unsigned int)pargs.r.ret_ulong, 0); break; @@ -1514,6 +1535,7 @@ main (int argc, char **argv) strerror (errno) ); agent_exit (1); } + ctrl->thread_startup.fd = GNUPG_INVALID_FD; ctrl->session_env = session_env_new (); if (!ctrl->session_env) { @@ -1579,7 +1601,7 @@ main (int argc, char **argv) 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); + handle_connections (fd, fd_extra, fd_browser, fd_ssh, 1); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) @@ -1807,14 +1829,14 @@ main (int argc, char **argv) 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; } + /* When we are running with a program given on the command + * line, the inotify things may not work well and thus + * we cannot avoid the regular stat calls. */ + if (argc) + reliable_homedir_inotify = 0; + { struct sigaction sa; @@ -1833,7 +1855,8 @@ main (int argc, char **argv) } log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13) ); - handle_connections (fd, fd_extra, fd_browser, fd_ssh); + handle_connections (fd, fd_extra, fd_browser, fd_ssh, + reliable_homedir_inotify); assuan_sock_close (fd); } @@ -1989,6 +2012,7 @@ agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); + clear_ephemeral_keys (ctrl); xfree (ctrl->digest.data); ctrl->digest.data = NULL; @@ -2134,39 +2158,45 @@ get_agent_active_connection_count (void) notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) +static void * +create_an_event (void) +{ + 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 an event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for an event failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + return h2; + } + + return INVALID_HANDLE_VALUE; +} + void * get_agent_daemon_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; - } - } + the_event = create_an_event (); return the_event; } @@ -2277,7 +2307,7 @@ create_server_socket (char *name, int primary, int cygwin, if (primary && !check_for_running_agent (1)) { if (steal_socket) - log_info (N_("trying to steal socket from running %s\n"), + log_info (_("trying to steal socket from running %s\n"), "gpg-agent"); else { @@ -2292,17 +2322,19 @@ create_server_socket (char *name, int primary, int cygwin, } } gnupg_remove (unaddr->sun_path); + log_info (_("socket file removed - retrying binding\n")); 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) { + rc = gpg_error_from_syserror (); + log_libassuan_system_error (fd); /* 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 ())); + unaddr->sun_path, gpg_strerror (rc)); assuan_sock_close (fd); *name = 0; /* Inhibit removal of the socket by cleanup(). */ @@ -2423,57 +2455,6 @@ create_directories (void) } - -/* 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", gpgrt_strusage(11), gpgrt_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) - && gnupg_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 @@ -2532,6 +2513,11 @@ handle_signal (int signo) agent_sigusr2_action (); break; + case SIGCONT: + /* Do nothing, but break the syscall. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); @@ -2569,7 +2555,7 @@ 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)); + FD_DBG (ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; @@ -2869,12 +2855,12 @@ do_start_connection_thread (ctrl_t ctrl) agent_init_default_ctrl (ctrl); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2949,12 +2935,12 @@ start_connection_thread_ssh (void *arg) 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)); + (unsigned long) npth_self(), FD_DBG (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)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2963,13 +2949,36 @@ start_connection_thread_ssh (void *arg) } +void +agent_kick_the_loop (void) +{ + /* Kick the select loop. */ +#ifdef HAVE_W32_SYSTEM + int ret = SetEvent (the_event2); + if (ret == 0) + log_error ("SetEvent for agent_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#else +# ifdef HAVE_PSELECT_NO_EINTR + write (event_pipe_fd, "", 1); +# else + int ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("sending signal for agent_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +# endif +#endif +} + + /* 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) + gnupg_fd_t listen_fd_ssh, + int reliable_homedir_inotify) { gpg_error_t err; npth_attr_t tattr; @@ -2980,12 +2989,15 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t fd; int nfd; int saved_errno; - struct timespec abstime; - struct timespec curtime; - struct timespec timeout; + struct timespec *tp; #ifdef HAVE_W32_SYSTEM - HANDLE events[2]; + HANDLE events[3]; unsigned int events_set; +#else + int signo; +# ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; +# endif #endif int sock_inotify_fd = -1; int home_inotify_fd = -1; @@ -3013,11 +3025,24 @@ handle_connections (gnupg_fd_t listen_fd, npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); + npth_sigev_add (SIGCONT); npth_sigev_add (SIGTERM); npth_sigev_fini (); +# ifdef HAVE_PSELECT_NO_EINTR + ret = gnupg_create_pipe (pipe_fd, 0); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + event_pipe_fd = pipe_fd[1]; +# else + main_thread_pid = getpid (); +# endif #else events[0] = get_agent_daemon_notify_event (); - events[1] = INVALID_HANDLE_VALUE; + events[1] = the_event2 = create_an_event (); + events[2] = INVALID_HANDLE_VALUE; #endif if (disable_check_own_socket) @@ -3029,7 +3054,7 @@ handle_connections (gnupg_fd_t listen_fd, gpg_strerror (err)); } - if (disable_check_own_socket) + if (!reliable_homedir_inotify) home_inotify_fd = -1; else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, gnupg_homedir ()))) @@ -3041,6 +3066,27 @@ handle_connections (gnupg_fd_t listen_fd, else have_homedir_inotify = 1; +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (!disable_check_own_socket && sock_inotify_fd == -1) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_own_socket_thread, NULL); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + } +#endif + + if ((HAVE_PARENT_PID_SUPPORT && parent_pid != (pid_t)(-1)) + || !have_homedir_inotify) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_others_thread, NULL); + if (err) + log_error ("error spawning check_others_thread: %s\n", strerror (err)); + } + /* 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). */ @@ -3070,24 +3116,24 @@ handle_connections (gnupg_fd_t listen_fd, FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); - nfd = FD2INT (listen_fd); + nfd = FD2NUM (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); + nfd = FD2NUM (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); + nfd = FD2NUM (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); + nfd = FD2NUM (listen_fd_ssh); } if (sock_inotify_fd != -1) { @@ -3107,15 +3153,12 @@ handle_connections (gnupg_fd_t listen_fd, 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) + if (active_connections == 0 || is_supervised) break; /* ready */ /* Do not accept new connections but keep on running the @@ -3144,28 +3187,23 @@ handle_connections (gnupg_fd_t listen_fd, 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); +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (nfd < pipe_fd[0]) + nfd = pipe_fd[0]; +#endif + + tp = agent_cache_expiration (); #ifndef HAVE_W32_SYSTEM - ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, tp, npth_sigev_sigmask ()); saved_errno = errno; - { - int signo; - while (npth_sigev_get_pending (&signo)) - handle_signal (signo); - } + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); #else - ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, tp, events, &events_set); saved_errno = errno; @@ -3181,11 +3219,47 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_sleep (1); continue; } + +#ifndef HAVE_W32_SYSTEM + if ((problem_detected & AGENT_PROBLEM_PARENT_HAS_GONE)) + { + shutdown_pending = 2; + log_info ("parent process died - shutting down\n"); + log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); + cleanup (); + agent_exit (0); + } +#endif + + if ((problem_detected & AGENT_PROBLEM_SOCKET_TAKEOVER)) + { + /* 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"); + } + + if ((problem_detected & AGENT_PROBLEM_HOMEDIR_REMOVED)) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } + if (ret <= 0) /* Interrupt or 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 + /* 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. */ @@ -3193,7 +3267,10 @@ handle_connections (gnupg_fd_t listen_fd, && FD_ISSET (sock_inotify_fd, &read_fdset) && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) { - shutdown_pending = 1; + /* We may not remove the socket (if any), as it may be now + in use by another server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; close (sock_inotify_fd); sock_inotify_fd = -1; log_info ("socket file has been removed - shutting down\n"); @@ -3222,12 +3299,14 @@ handle_connections (gnupg_fd_t listen_fd, continue; plen = sizeof paddr; - fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), - (struct sockaddr *)&paddr, &plen)); + fd = assuan_sock_accept (listentbl[idx].l_fd, + (struct sockaddr *)&paddr, &plen); if (fd == GNUPG_INVALID_FD) { + gpg_error_t myerr = gpg_error_from_syserror (); + log_libassuan_system_error (listentbl[idx].l_fd); log_error ("accept failed for %s: %s\n", - listentbl[idx].name, strerror (errno)); + listentbl[idx].name, gpg_strerror (myerr)); } else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) { @@ -3263,13 +3342,21 @@ handle_connections (gnupg_fd_t listen_fd, close (sock_inotify_fd); if (home_inotify_fd != -1) close (home_inotify_fd); +#ifdef HAVE_W32_SYSTEM + if (the_event2 != INVALID_HANDLE_VALUE) + CloseHandle (the_event2); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif cleanup (); log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13)); npth_attr_destroy (&tattr); } - +#if CHECK_OWN_SOCKET_INTERVAL > 0 /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) @@ -3280,20 +3367,18 @@ check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) } -/* 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) +/* 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 int +do_check_own_socket (const char *sockname) { 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) { @@ -3331,58 +3416,78 @@ check_own_socket_thread (void *arg) 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; + + return rc; } - -/* 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) +/* The thread running the actual check. */ +static void * +check_own_socket_thread (void *arg) { 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. */ + (void)arg; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) - return; /* Out of memory. */ + return NULL; /* Out of memory. */ - err = npth_attr_init (&tattr); - if (err) + while (!problem_detected) { - xfree (sockname); - return; + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL); + + if (do_check_own_socket (sockname)) + problem_detected |= AGENT_PROBLEM_SOCKET_TAKEOVER; } - 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); + + agent_kick_the_loop (); + + leave: + xfree (sockname); + return NULL; } +#endif +/* The thread running other checks. */ +static void * +check_others_thread (void *arg) +{ + const char *homedir = gnupg_homedir (); + (void)arg; + + while (!problem_detected) + { + struct stat statbuf; + + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_PROBLEMS_INTERVAL); + + /* 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) && kill (parent_pid, 0)) + problem_detected |= AGENT_PROBLEM_PARENT_HAS_GONE; +#endif /*HAVE_W32_SYSTEM*/ + + /* Check whether the homedir is still available. */ + if (!have_homedir_inotify + && gnupg_stat (homedir, &statbuf) && errno == ENOENT) + problem_detected |= AGENT_PROBLEM_HOMEDIR_REMOVED; + } + + agent_kick_the_loop (); + + leave: + return NULL; +} /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. diff --git a/agent/gpg-agent.w32-manifest.in b/agent/gpg-agent.w32-manifest.in new file mode 100644 index 000000000..7991a9699 --- /dev/null +++ b/agent/gpg-agent.w32-manifest.in @@ -0,0 +1,25 @@ + + +GNU Privacy Guard (Private Key Daemon) + + + + + + + + + + + + + + + + + + diff --git a/agent/learncard.c b/agent/learncard.c index 678ff9b96..351f59a2b 100644 --- a/agent/learncard.c +++ b/agent/learncard.c @@ -295,10 +295,14 @@ send_cert_back (ctrl_t ctrl, const char *id, void *assuan_context) return 0; } + /* Perform the learn operation. If ASSUAN_CONTEXT is not NULL and - SEND is true all new certificates are send back via Assuan. */ + * SEND is true all new certificates are send back via Assuan. If + * DEMAND_SN is not NULL it has a string with the serial number of the + * card requested. */ int -agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) +agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force, + const char *demand_sn) { int rc; struct kpinfo_cb_parm_s parm; @@ -328,7 +332,7 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) cparm.ctrl = ctrl; /* Now gather all the available info. */ - rc = agent_card_learn (ctrl, kpinfo_cb, &parm, certinfo_cb, &cparm, + rc = agent_card_learn (ctrl, demand_sn, kpinfo_cb, &parm, certinfo_cb, &cparm, sinfo_cb, &sparm); if (!rc && (parm.error || cparm.error || sparm.error)) rc = parm.error? parm.error : cparm.error? cparm.error : sparm.error; @@ -397,7 +401,7 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) for (p=item->hexgrip, i=0; i < 20; p += 2, i++) grip[i] = xtoi_2 (p); - if (!force && !agent_key_available (grip)) + if (!force && !agent_key_available (ctrl, grip)) continue; /* The key is already available. */ /* Unknown key - store it. */ @@ -408,7 +412,17 @@ agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force) goto leave; } - rc = agent_write_shadow_key (grip, serialno, item->id, pubkey, force); + if (!ctrl->ephemeral_mode) + { + char *dispserialno; + + agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, + item->hexgrip); + rc = agent_write_shadow_key (ctrl, + grip, serialno, item->id, pubkey, force, + dispserialno); + xfree (dispserialno); + } xfree (pubkey); if (rc) goto leave; diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index c26f21d35..fc2e84c13 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -27,6 +27,8 @@ #include #include "agent.h" +#include "../common/openpgpdefs.h" +#include "../common/util.h" /* DECRYPT the stuff in ciphertext which is expected to be a S-Exp. @@ -41,7 +43,6 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL; unsigned char *shadow_info = NULL; gpg_error_t err = 0; - int no_shadow_info = 0; char *buf = NULL; size_t len; @@ -70,17 +71,13 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, err = agent_key_from_file (ctrl, NULL, desc_text, NULL, &shadow_info, CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL); - if (gpg_err_code (err) == GPG_ERR_NO_SECKEY) - no_shadow_info = 1; - else if (err) + if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY) { log_error ("failed to read the secret key\n"); - goto leave; } - - if (shadow_info || no_shadow_info) + else if (shadow_info + || err /* gpg_err_code (err) == GPG_ERR_NO_SECKEY */) { /* divert operation to the smartcard */ - if (!gcry_sexp_canon_len (ciphertext, ciphertextlen, NULL, NULL)) { err = gpg_error (GPG_ERR_INV_SEXP); @@ -95,12 +92,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, &buf, &len, r_padding); if (err) { - /* We restore the original error (ie. no seckey) is no card + /* We restore the original error (ie. no seckey) as no card * has been found and we have no shadow key. This avoids a * surprising "card removed" error code. */ if ((gpg_err_code (err) == GPG_ERR_CARD_REMOVED || gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) - && no_shadow_info) + && !shadow_info) err = gpg_error (GPG_ERR_NO_SECKEY); else log_error ("smartcard decryption failed: %s\n", gpg_strerror (err)); @@ -157,3 +154,781 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, xfree (shadow_info); return err; } + + +/* Reverse BUFFER to change the endianness. */ +static void +reverse_buffer (unsigned char *buffer, unsigned int length) +{ + unsigned int tmp, i; + + for (i=0; i < length/2; i++) + { + tmp = buffer[i]; + buffer[i] = buffer[length-1-i]; + buffer[length-1-i] = tmp; + } +} + + +static gpg_error_t +ecc_extract_pk_from_key (const struct gnupg_ecc_params *ecc, + gcry_sexp_t s_skey, unsigned char *ecc_pk) +{ + gpg_error_t err; + unsigned int nbits; + const unsigned char *p; + size_t len; + gcry_mpi_t ecc_pk_mpi = NULL; + + err = gcry_sexp_extract_param (s_skey, NULL, "/q", &ecc_pk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting q and d from ECC key failed\n", __func__); + return err; + } + + p = gcry_mpi_get_opaque (ecc_pk_mpi, &nbits); + len = (nbits+7)/8; + if (len != ecc->pubkey_len) + { + if (opt.verbose) + log_info ("%s: ECC public key length invalid (%zu)\n", __func__, len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + else if (len == ecc->point_len) + memcpy (ecc_pk, p, ecc->point_len); + else if (len == ecc->point_len + 1 && p[0] == 0x40) + /* Remove the 0x40 prefix (for Curve25519) */ + memcpy (ecc_pk, p+1, ecc->point_len); + else + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex (ecc_pk, ecc->pubkey_len, "ECC pubkey:"); + + leave: + mpi_release (ecc_pk_mpi); + return err; +} + +static gpg_error_t +ecc_extract_sk_from_key (const struct gnupg_ecc_params *ecc, + gcry_sexp_t s_skey, unsigned char *ecc_sk) +{ + gpg_error_t err; + unsigned int nbits; + const unsigned char *p; + size_t len; + gcry_mpi_t ecc_sk_mpi = NULL; + + err = gcry_sexp_extract_param (s_skey, NULL, "/d", &ecc_sk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting d from ECC key failed\n", __func__); + return err; + } + + p = gcry_mpi_get_opaque (ecc_sk_mpi, &nbits); + len = (nbits+7)/8; + if (len > ecc->scalar_len) + { + if (opt.verbose) + log_info ("%s: ECC secret key too long (%zu)\n", __func__, len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + memset (ecc_sk, 0, ecc->scalar_len - len); + memcpy (ecc_sk + ecc->scalar_len - len, p, len); + if (ecc->scalar_reverse) + reverse_buffer (ecc_sk, ecc->scalar_len); + mpi_release (ecc_sk_mpi); + ecc_sk_mpi = NULL; + + if (DBG_CRYPTO) + log_printhex (ecc_sk, ecc->scalar_len, "ECC seckey:"); + + leave: + mpi_release (ecc_sk_mpi); + return err; +} + +static gpg_error_t +ecc_raw_kem (const struct gnupg_ecc_params *ecc, gcry_sexp_t s_skey, + const unsigned char *ecc_ct, unsigned char *ecc_ecdh) +{ + gpg_error_t err = 0; + unsigned char ecc_sk[ECC_SCALAR_LEN_MAX]; + + if (ecc->scalar_len > ECC_SCALAR_LEN_MAX) + { + if (opt.verbose) + log_info ("%s: ECC scalar length invalid (%zu)\n", + __func__, ecc->scalar_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + err = ecc_extract_sk_from_key (ecc, s_skey, ecc_sk); + if (err) + goto leave; + + err = gcry_kem_decap (ecc->kem_algo, ecc_sk, ecc->scalar_len, + ecc_ct, ecc->point_len, ecc_ecdh, ecc->point_len, + NULL, 0); + if (err) + { + if (opt.verbose) + log_info ("%s: gcry_kem_decap for ECC failed\n", __func__); + } + + leave: + wipememory (ecc_sk, sizeof ecc_sk); + + return err; +} + +static gpg_error_t +get_cardkey (ctrl_t ctrl, const char *keygrip, gcry_sexp_t *r_s_pk) +{ + gpg_error_t err; + unsigned char *pkbuf; + size_t pkbuflen; + + err = agent_card_readkey (ctrl, keygrip, &pkbuf, NULL); + if (err) + return err; + + pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); + err = gcry_sexp_sscan (r_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; +} + +static gpg_error_t +ecc_get_curve (ctrl_t ctrl, gcry_sexp_t s_skey, const char **r_curve) +{ + gpg_error_t err = 0; + gcry_sexp_t s_skey_card = NULL; + const char *curve = NULL; + gcry_sexp_t key; + + *r_curve = NULL; + + if (!s_skey) + { + err = get_cardkey (ctrl, ctrl->keygrip, &s_skey_card); + if (err) + goto leave; + + key = s_skey_card; + } + else + key = s_skey; + + curve = get_ecc_curve_from_key (key); + if (!curve) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto leave; + } + + *r_curve = curve; + + leave: + gcry_sexp_release (s_skey_card); + return err; +} + +/* Given a private key in SEXP by S_SKEY0 and a cipher text by ECC_CT + * with length ECC_POINT_LEN, do ECC KEM decap (== raw ECDH) + * operation. Result is returned in the memory referred by ECC_ECDH. + * Public key is extracted and put into ECC_PK. The pointer to ECC + * parameters is stored into R_ECC. SHADOW_INFO0 is used to determine + * if the private key is actually on smartcard. CTRL is used to + * access smartcard, internally. */ +static gpg_error_t +ecc_pgp_kem_decap (ctrl_t ctrl, gcry_sexp_t s_skey0, + const unsigned char *shadow_info0, + const unsigned char *ecc_ct, size_t ecc_point_len, + unsigned char ecc_ecdh[ECC_POINT_LEN_MAX], + unsigned char ecc_pk[ECC_POINT_LEN_MAX], + const struct gnupg_ecc_params **r_ecc) +{ + gpg_error_t err; + const char *curve; + const struct gnupg_ecc_params *ecc = NULL; + + if (ecc_point_len > ECC_POINT_LEN_MAX) + return gpg_error (GPG_ERR_INV_DATA); + + err = ecc_get_curve (ctrl, s_skey0, &curve); + if (err) + { + if ((gpg_err_code (err) == GPG_ERR_CARD_REMOVED + || gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) + && !s_skey0) + err = gpg_error (GPG_ERR_NO_SECKEY); + return err; + } + + if (DBG_CRYPTO) + log_debug ("ECC curve: %s\n", curve); + + ecc = gnupg_get_ecc_params (curve); + if (!ecc) + { + if (opt.verbose) + log_info ("%s: curve '%s' not supported\n", __func__, curve); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + *r_ecc = ecc; + + if (ecc->may_have_prefix && ecc_point_len == ecc->point_len + 1 + && *ecc_ct == 0x40) + { + ecc_ct++; + ecc_point_len--; + } + + if (ecc->point_len != ecc_point_len) + { + if (opt.verbose) + log_info ("%s: ECC cipher text length invalid (%zu != %zu)\n", + __func__, ecc->point_len, ecc_point_len); + return gpg_error (GPG_ERR_INV_DATA); + } + + err = ecc_extract_pk_from_key (ecc, s_skey0, ecc_pk); + if (err) + return err; + + if (DBG_CRYPTO) + log_printhex (ecc_ct, ecc->point_len, "ECC ephem:"); + + if (shadow_info0 || !s_skey0) + { + if (s_skey0 && agent_is_tpm2_key (s_skey0)) + { + err = agent_tpm2d_ecc_kem (ctrl, shadow_info0, + ecc_ct, ecc->point_len, ecc_ecdh); + if (err) + { + log_error ("TPM decryption failed: %s\n", gpg_strerror (err)); + return err; + } + } + else + { + err = agent_card_ecc_kem (ctrl, ecc_ct, ecc->point_len, ecc_ecdh); + if (err) + { + log_error ("smartcard decryption failed: %s\n", + gpg_strerror (err)); + return err; + } + } + } + else + err = ecc_raw_kem (ecc, s_skey0, ecc_ct, ecc_ecdh); + + if (err) + return err; + + if (DBG_CRYPTO) + log_printhex (ecc_ecdh, ecc_point_len, "ECC ecdh:"); + + return 0; +} + +/* For composite PGP KEM (ECC+ML-KEM), decrypt CIPHERTEXT using KEM API. + First keygrip is for ECC, second keygrip is for PQC. CIPHERTEXT + should follow the format of: + + (enc-val(pqc(c%d)(e%m)(k%m)(s%m)(fixed-info&))) + c: cipher identifier (of session key (wrapped key)) + e: ECDH ciphertext + k: ML-KEM ciphertext + s: encrypted session key + fixed-info: A buffer with the fixed info. + + FIXME: For now, possible PQC key on smartcard is not yet supported. + */ +static gpg_error_t +composite_pgp_kem_decrypt (ctrl_t ctrl, const char *desc_text, + gcry_sexp_t s_cipher, membuf_t *outbuf) +{ + gcry_sexp_t s_skey0 = NULL; + gcry_sexp_t s_skey1 = NULL; + unsigned char *shadow_info0 = NULL; + unsigned char *shadow_info1 = NULL; + gpg_error_t err = 0; + + unsigned int nbits; + size_t len; + + int algo; + gcry_mpi_t encrypted_sessionkey_mpi = NULL; + const unsigned char *encrypted_sessionkey; + size_t encrypted_sessionkey_len; + + gcry_mpi_t ecc_ct_mpi = NULL; + const unsigned char *ecc_ct; + size_t ecc_ct_len; + unsigned char ecc_ecdh[ECC_POINT_LEN_MAX]; + unsigned char ecc_pk[ECC_POINT_LEN_MAX]; + unsigned char ecc_ss[ECC_HASH_LEN_MAX]; + int ecc_hashalgo; + size_t ecc_shared_len, ecc_point_len; + const struct gnupg_ecc_params *ecc; + + enum gcry_kem_algos mlkem_kem_algo; + gcry_mpi_t mlkem_sk_mpi = NULL; + gcry_mpi_t mlkem_ct_mpi = NULL; + const unsigned char *mlkem_sk; + size_t mlkem_sk_len; + const unsigned char *mlkem_ct; + size_t mlkem_ct_len; + unsigned char mlkem_ss[GCRY_KEM_MLKEM1024_SHARED_LEN]; + size_t mlkem_ss_len; + + unsigned char kek[32]; + size_t kek_len = 32; /* AES-256 is mandatory */ + + gcry_cipher_hd_t hd; + unsigned char sessionkey[256]; + size_t sessionkey_len; + gcry_buffer_t fixed_info = { 0, 0, 0, NULL }; + + err = agent_key_from_file (ctrl, NULL, desc_text, + NULL, &shadow_info0, + CACHE_MODE_NORMAL, NULL, &s_skey0, NULL, NULL); + if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY) + { + log_error ("failed to read the secret key\n"); + goto leave; + } + + err = agent_key_from_file (ctrl, NULL, desc_text, + ctrl->keygrip1, &shadow_info1, + CACHE_MODE_NORMAL, NULL, &s_skey1, NULL, NULL); + /* Here assumes no smartcard for ML-KEM, but private key in a file. */ + if (err) + { + log_error ("failed to read the another secret key\n"); + goto leave; + } + + err = gcry_sexp_extract_param (s_cipher, NULL, "%dc/eks&'fixed-info'", + &algo, &ecc_ct_mpi, &mlkem_ct_mpi, + &encrypted_sessionkey_mpi, &fixed_info, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting parameters failed\n", __func__); + goto leave; + } + + ecc_ct = gcry_mpi_get_opaque (ecc_ct_mpi, &nbits); + ecc_ct_len = (nbits+7)/8; + + len = gcry_cipher_get_algo_keylen (algo); + encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi, &nbits); + encrypted_sessionkey_len = (nbits+7)/8; + if (len == 0 || encrypted_sessionkey_len != len + 8) + { + if (opt.verbose) + log_info ("%s: encrypted session key length %zu" + " does not match the length for algo %d\n", + __func__, encrypted_sessionkey_len, algo); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + /* Firstly, ECC part. */ + ecc_point_len = ecc_ct_len; + err = ecc_pgp_kem_decap (ctrl, s_skey0, shadow_info0, ecc_ct, ecc_point_len, + ecc_ecdh, ecc_pk, &ecc); + if (err) + goto leave; + ecc_hashalgo = ecc->hash_algo; + ecc_shared_len = gcry_md_get_algo_dlen (ecc_hashalgo); + err = gnupg_ecc_kem_kdf (ecc_ss, ecc_shared_len, ecc_hashalgo, + ecc_ecdh, ecc_point_len, ecc_ct, ecc_point_len, + ecc_pk, ecc_point_len, NULL, 0); + if (err) + { + if (opt.verbose) + log_info ("%s: kdf for ECC failed\n", __func__); + goto leave; + } + wipememory (ecc_ecdh, sizeof ecc_ecdh); + if (DBG_CRYPTO) + log_printhex (ecc_ss, ecc_shared_len, "ECC shared:"); + + /* Secondly, PQC part. For now, we assume ML-KEM. */ + err = gcry_sexp_extract_param (s_skey1, NULL, "/s", &mlkem_sk_mpi, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting s from PQ key failed\n", __func__); + goto leave; + } + mlkem_sk = gcry_mpi_get_opaque (mlkem_sk_mpi, &nbits); + mlkem_sk_len = (nbits+7)/8; + if (mlkem_sk_len == GCRY_KEM_MLKEM512_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM512; + mlkem_ss_len = GCRY_KEM_MLKEM512_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM512_CIPHER_LEN; + } + else if (mlkem_sk_len == GCRY_KEM_MLKEM768_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM768; + mlkem_ss_len = GCRY_KEM_MLKEM768_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM768_CIPHER_LEN; + } + else if (mlkem_sk_len == GCRY_KEM_MLKEM1024_SECKEY_LEN) + { + mlkem_kem_algo = GCRY_KEM_MLKEM1024; + mlkem_ss_len = GCRY_KEM_MLKEM1024_SHARED_LEN; + mlkem_ct_len = GCRY_KEM_MLKEM1024_CIPHER_LEN; + } + else + { + if (opt.verbose) + log_info ("%s: PQ key length invalid (%zu)\n", __func__, mlkem_sk_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + mlkem_ct = gcry_mpi_get_opaque (mlkem_ct_mpi, &nbits); + len = (nbits+7)/8; + if (len != mlkem_ct_len) + { + if (opt.verbose) + log_info ("%s: PQ cipher text length invalid (%zu)\n", + __func__, mlkem_ct_len); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + err = gcry_kem_decap (mlkem_kem_algo, mlkem_sk, mlkem_sk_len, + mlkem_ct, mlkem_ct_len, mlkem_ss, mlkem_ss_len, + NULL, 0); + if (err) + { + if (opt.verbose) + log_info ("%s: gcry_kem_decap for PQ failed\n", __func__); + goto leave; + } + + mpi_release (mlkem_sk_mpi); + mlkem_sk_mpi = NULL; + + /* Then, combine two shared secrets and ciphertexts into one KEK */ + err = gnupg_kem_combiner (kek, kek_len, + ecc_ss, ecc_shared_len, ecc_ct, ecc_point_len, + mlkem_ss, mlkem_ss_len, mlkem_ct, mlkem_ct_len, + fixed_info.data, fixed_info.size); + if (err) + { + if (opt.verbose) + log_info ("%s: KEM combiner failed\n", __func__); + goto leave; + } + + mpi_release (ecc_ct_mpi); + ecc_ct_mpi = NULL; + mpi_release (mlkem_ct_mpi); + mlkem_ct_mpi = NULL; + + if (DBG_CRYPTO) + { + log_printhex (kek, kek_len, "KEK key: "); + } + + err = gcry_cipher_open (&hd, GCRY_CIPHER_AES256, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + { + if (opt.verbose) + log_error ("ecdh failed to initialize AESWRAP: %s\n", + gpg_strerror (err)); + goto leave; + } + + err = gcry_cipher_setkey (hd, kek, kek_len); + sessionkey_len = encrypted_sessionkey_len - 8; + if (!err) + err = gcry_cipher_decrypt (hd, sessionkey, sessionkey_len, + encrypted_sessionkey, encrypted_sessionkey_len); + gcry_cipher_close (hd); + + mpi_release (encrypted_sessionkey_mpi); + encrypted_sessionkey_mpi = NULL; + + if (err) + { + log_error ("KEM decrypt failed: %s\n", gpg_strerror (err)); + goto leave; + } + + put_membuf_printf (outbuf, + "(5:value%u:", (unsigned int)sessionkey_len); + put_membuf (outbuf, sessionkey, sessionkey_len); + put_membuf (outbuf, ")", 2); + + leave: + wipememory (ecc_ss, sizeof ecc_ss); + wipememory (mlkem_ss, sizeof mlkem_ss); + wipememory (kek, sizeof kek); + wipememory (sessionkey, sizeof sessionkey); + + mpi_release (ecc_ct_mpi); + mpi_release (mlkem_sk_mpi); + mpi_release (mlkem_ct_mpi); + mpi_release (encrypted_sessionkey_mpi); + gcry_free (fixed_info.data); + gcry_sexp_release (s_skey0); + gcry_sexp_release (s_skey1); + xfree (shadow_info0); + xfree (shadow_info1); + return err; +} + +/* For ECC PGP KEM, decrypt CIPHERTEXT using KEM API. CIPHERTEXT + should follow the format of: + + (enc-val(ecc(c%d)(h%d)(e%m)(s%m)(kdf-params&))) + c: cipher identifier (of wrapping key) + h: hash identifier + e: ECDH ciphertext + s: encrypted session key + fixed-info: A buffer with the fixed info (the KDF parameters). + + */ +static gpg_error_t +ecc_kem_decrypt (ctrl_t ctrl, const char *desc_text, + gcry_sexp_t s_cipher, membuf_t *outbuf) +{ + gcry_sexp_t s_skey = NULL; + unsigned char *shadow_info = NULL; + gpg_error_t err = 0; + + unsigned int nbits; + + int algo; + int hashalgo; + gcry_mpi_t encrypted_sessionkey_mpi = NULL; + const unsigned char *encrypted_sessionkey; + size_t encrypted_sessionkey_len; + + gcry_mpi_t ecc_ct_mpi = NULL; + const unsigned char *ecc_ct; + size_t ecc_ct_len; + unsigned char ecc_ecdh[ECC_POINT_LEN_MAX]; + unsigned char ecc_pk[ECC_POINT_LEN_MAX]; + size_t ecc_point_len; + const struct gnupg_ecc_params *ecc; + + unsigned char *kek = NULL; + size_t kek_len; + + gcry_cipher_hd_t hd; + unsigned char sessionkey[256]; + size_t sessionkey_len; + gcry_buffer_t kdf_params = { 0, 0, 0, NULL }; + + err = agent_key_from_file (ctrl, NULL, desc_text, + NULL, &shadow_info, + CACHE_MODE_NORMAL, NULL, &s_skey, NULL, NULL); + if (err && gpg_err_code (err) != GPG_ERR_NO_SECKEY) + { + log_error ("failed to read the secret key\n"); + goto leave; + } + + err = gcry_sexp_extract_param (s_cipher, NULL, "%dc%dh/es&'kdf-params'", + &algo, &hashalgo, &ecc_ct_mpi, + &encrypted_sessionkey_mpi, &kdf_params, NULL); + if (err) + { + if (opt.verbose) + log_info ("%s: extracting parameters failed\n", __func__); + goto leave; + } + + if (!kdf_params.data) + { + if (opt.verbose) + log_info ("%s: the KDF parameters is required\n", __func__); + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + ecc_ct = gcry_mpi_get_opaque (ecc_ct_mpi, &nbits); + ecc_ct_len = (nbits+7)/8; + + encrypted_sessionkey = gcry_mpi_get_opaque (encrypted_sessionkey_mpi, &nbits); + encrypted_sessionkey_len = (nbits+7)/8; + + kek_len = gcry_cipher_get_algo_keylen (algo); + if (kek_len == 0 || kek_len > gcry_md_get_algo_dlen (hashalgo)) + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + kek = xtrymalloc (kek_len); + if (!kek) + { + err = gpg_error_from_syserror (); + goto leave; + } + + ecc_point_len = ecc_ct_len; + err = ecc_pgp_kem_decap (ctrl, s_skey, shadow_info, + ecc_ct, ecc_point_len, + ecc_ecdh, ecc_pk, &ecc); + if (err) + goto leave; + err = gnupg_ecc_kem_kdf (kek, kek_len, hashalgo, + ecc->point_len > ecc->scalar_len ? + /* For Weierstrass curve, extract + x-component from the point. */ + ecc_ecdh + 1 : ecc_ecdh, + ecc->scalar_len, ecc_ct, ecc_point_len, + ecc_pk, ecc_point_len, + (char *)kdf_params.data+kdf_params.off, + kdf_params.len); + if (err) + { + if (opt.verbose) + log_info ("%s: kdf for ECC failed\n", __func__); + goto leave; + } + wipememory (ecc_ecdh, sizeof ecc_ecdh); + if (DBG_CRYPTO) + { + log_printhex (kek, kek_len, "KEK key: "); + } + + err = gcry_cipher_open (&hd, algo, GCRY_CIPHER_MODE_AESWRAP, 0); + if (err) + { + if (opt.verbose) + log_error ("ecdh failed to initialize AESWRAP: %s\n", + gpg_strerror (err)); + goto leave; + } + + if (encrypted_sessionkey[0] != encrypted_sessionkey_len - 1) + { + err = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + + err = gcry_cipher_setkey (hd, kek, kek_len); + sessionkey_len = encrypted_sessionkey_len - 8 - 1; + if (!err) + err = gcry_cipher_decrypt (hd, sessionkey, sessionkey_len, + encrypted_sessionkey + 1, + encrypted_sessionkey_len - 1); + gcry_cipher_close (hd); + + if (err) + { + log_error ("KEM decrypt failed: %s\n", gpg_strerror (err)); + goto leave; + } + + put_membuf_printf (outbuf, + "(5:value%u:", (unsigned int)sessionkey_len); + put_membuf (outbuf, sessionkey, sessionkey_len); + put_membuf (outbuf, ")", 2); + + leave: + wipememory (sessionkey, sizeof sessionkey); + wipememory (kek, sizeof kek); + xfree (kek); + mpi_release (ecc_ct_mpi); + mpi_release (encrypted_sessionkey_mpi); + gcry_free (kdf_params.data); + gcry_sexp_release (s_skey); + xfree (shadow_info); + return err; +} + + +/* DECRYPT the encrypted stuff (like encrypted session key) in + * CIPHERTEXT using KEM API, with KEMID. Keys (or a key) are + * specified in CTRL. DESC_TEXT is used to retrieve private key. + * OPTION can be specified for upper layer option for KEM. Decrypted + * stuff (like session key) is written to OUTBUF. For now, + * KEMID==KEM_CMS is _not_ yet supported. + */ +gpg_error_t +agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid, + const unsigned char *ciphertext, size_t ciphertextlen, + const unsigned char *option, size_t optionlen, + membuf_t *outbuf) +{ + gcry_sexp_t s_cipher = NULL; + gpg_error_t err = 0; + + (void)optionlen; + + err = gcry_sexp_sscan (&s_cipher, NULL, (char*)ciphertext, ciphertextlen); + if (err) + { + log_error ("failed to convert ciphertext: %s\n", gpg_strerror (err)); + return gpg_error (GPG_ERR_INV_DATA); + } + + if (option) + { + log_error ("KEM (%d) requires no option\n", kemid); + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + if (kemid == KEM_PGP) + err = ecc_kem_decrypt (ctrl, desc_text, s_cipher, outbuf); + else if (kemid == KEM_PQC_PGP) + { + if (!ctrl->have_keygrip) + { + log_error ("speculative decryption not yet supported\n"); + return gpg_error (GPG_ERR_NO_SECKEY); + } + + if (!ctrl->have_keygrip1) + { + log_error ("Composite KEM requires two KEYGRIPs\n"); + return gpg_error (GPG_ERR_NO_SECKEY); + } + + if (DBG_CRYPTO) + { + log_printhex (ctrl->keygrip, 20, "keygrip0:"); + log_printhex (ctrl->keygrip1, 20, "keygrip1:"); + gcry_log_debugsxp ("cipher", s_cipher); + } + + err = composite_pgp_kem_decrypt (ctrl, desc_text, s_cipher, outbuf); + } + + leave: + gcry_sexp_release (s_cipher); + return err; +} diff --git a/agent/pksign.c b/agent/pksign.c index dfed0e398..322918969 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -371,9 +371,17 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, goto leave; } - if (keyref) - agent_write_shadow_key (ctrl->keygrip, serialno, keyref, pkbuf, 0); + if (keyref && !ctrl->ephemeral_mode) + { + char *dispserialno; + agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, + hexgrip); + agent_write_shadow_key (ctrl, + ctrl->keygrip, serialno, keyref, pkbuf, + 0, dispserialno); + xfree (dispserialno); + } algo = get_pk_algo_from_key (s_pkey); xfree (serialno); diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 87cf36814..c6450a20e 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -755,8 +755,9 @@ release_passphrase (char *pw) /* Stub function. */ int -agent_key_available (const unsigned char *grip) +agent_key_available (ctrl_t ctrl, const unsigned char *grip) { + (void)ctrl; (void)grip; return -1; /* Not available. */ } @@ -813,19 +814,21 @@ agent_askpin (ctrl_t ctrl, /* Replacement for the function in findkey.c. Here we write the key * to stdout. */ -int -agent_write_private_key (const unsigned char *grip, +gpg_error_t +agent_write_private_key (ctrl_t ctrl, const unsigned char *grip, const void *buffer, size_t length, int force, const char *serialno, const char *keyref, - time_t timestamp) + const char *dispserialno, time_t timestamp) { char hexgrip[40+4+1]; char *p; + (void)ctrl; (void)force; (void)serialno; (void)keyref; (void)timestamp; + (void)dispserialno; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); diff --git a/agent/protect.c b/agent/protect.c index 7197cf7e6..6c9bbaebc 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -509,7 +509,7 @@ do_encryption (const unsigned char *hashbegin, size_t hashlen, ((sha1 salt no_of_iterations) 16byte_iv) encrypted_octet_string) - in canoncical format of course. We use asprintf and %n modifier + in canonical format of course. We use asprintf and %n modifier and dummy values as placeholders. */ { char countbuf[35]; diff --git a/agent/sexp-secret.c b/agent/sexp-secret.c index ac8daa910..661681620 100644 --- a/agent/sexp-secret.c +++ b/agent/sexp-secret.c @@ -22,7 +22,7 @@ #include "../common/sexp-parse.h" /* - * When it's for ECC, fixup private key part in the cannonical SEXP + * When it's for ECC, fixup private key part in the canonical SEXP * representation in BUF. If not ECC, do nothing. */ gpg_error_t diff --git a/agent/trustlist.c b/agent/trustlist.c index 330f233b8..9831d04ef 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -38,14 +38,14 @@ struct trustitem_s { struct { - int disabled:1; /* This entry is disabled. */ - int for_pgp:1; /* Set by '*' or 'P' as first flag. */ - int for_smime:1; /* Set by '*' or 'S' as first flag. */ - int relax:1; /* Relax checking of root certificate + unsigned int disabled:1; /* This entry is disabled. */ + unsigned int for_pgp:1; /* Set by '*' or 'P' as first flag. */ + unsigned int for_smime:1; /* Set by '*' or 'S' as first flag. */ + unsigned int relax:1; /* Relax checking of root certificate constraints. */ - int cm:1; /* Use chain model for validation. */ - int qual:1; /* Root CA for qualified signatures. */ - int de_vs:1; /* Root CA for de-vs compliant PKI. */ + unsigned int cm:1; /* Use chain model for validation. */ + unsigned int qual:1; /* Root CA for qualified signatures. */ + unsigned int de_vs:1; /* Root CA for de-vs compliant PKI. */ } flags; unsigned char fpr[20]; /* The binary fingerprint. */ }; @@ -63,7 +63,7 @@ static const char headerblurb[] = "# well as empty lines are ignored. Lines have a length limit but this\n" "# is not a serious limitation as the format of the entries is fixed and\n" "# checked by gpg-agent. A non-comment line starts with optional white\n" -"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n" +"# space, followed by the SHA-1 fingerprint in hex, followed by a flag\n" "# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n" "# other flags. The fingerprint may be prefixed with a '!' to mark the\n" "# key as not trusted. You should give the gpg-agent a HUP or run the\n" @@ -325,7 +325,7 @@ read_one_trustfile (const char *fname, int systrust, ti->flags.cm = 1; else if (n == 4 && !memcmp (p, "qual", 4) && systrust) ti->flags.qual = 1; - else if (n == 4 && !memcmp (p, "de-vs", 4) && systrust) + else if (n == 5 && !memcmp (p, "de-vs", 5) && systrust) ti->flags.de_vs = 1; else log_error ("flag '%.*s' in '%s', line %d ignored\n", @@ -430,10 +430,13 @@ read_trustfiles (void) /* Check whether the given fpr is in our trustdb. We expect FPR to be - an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is - true the function assumes that the trusttable is already locked. */ + * an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is + * true the function assumes that the trusttable is already locked. + * If LISTMODE is set, a status line TRUSTLISTFPR is emitted first and + * disabled keys are not listed. + */ static gpg_error_t -istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, +istrusted_internal (ctrl_t ctrl, const char *fpr, int listmode, int *r_disabled, int already_locked) { gpg_error_t err = 0; @@ -472,6 +475,8 @@ istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, for (ti=trusttable, len = trusttablesize; len; ti++, len--) if (!memcmp (ti->fpr, fprbin, 20)) { + if (listmode && ti->flags.disabled) + continue; if (ti->flags.disabled && r_disabled) *r_disabled = 1; @@ -479,13 +484,19 @@ istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, in a locked state. */ if (already_locked) ; - else if (ti->flags.relax || ti->flags.cm || ti->flags.qual - || ti->flags.de_vs) + else if (listmode || ti->flags.relax || ti->flags.cm + || ti->flags.qual || ti->flags.de_vs) { unlock_trusttable (); locked = 0; err = 0; - if (ti->flags.relax) + if (listmode) + { + char hexfpr[2*20+1]; + bin2hex (ti->fpr, 20, hexfpr); + err = agent_write_status (ctrl,"TRUSTLISTFPR", hexfpr,NULL); + } + if (!err && ti->flags.relax) err = agent_write_status (ctrl,"TRUSTLISTFLAG", "relax",NULL); if (!err && ti->flags.cm) err = agent_write_status (ctrl,"TRUSTLISTFLAG", "cm", NULL); @@ -514,20 +525,24 @@ istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled, gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled) { - return istrusted_internal (ctrl, fpr, r_disabled, 0); + return istrusted_internal (ctrl, fpr, 0, r_disabled, 0); } /* Write all trust entries to FP. */ gpg_error_t -agent_listtrusted (void *assuan_context) +agent_listtrusted (ctrl_t ctrl, void *assuan_context, int status_mode) { trustitem_t *ti; char key[51]; + int table_locked; gpg_error_t err; size_t len; + strlist_t allhexgrips = NULL; + strlist_t sl; lock_trusttable (); + table_locked = 1; if (!trusttable) { err = read_trustfiles (); @@ -539,6 +554,7 @@ agent_listtrusted (void *assuan_context) } } + err = 0; if (trusttable) { for (ti=trusttable, len = trusttablesize; len; ti++, len--) @@ -546,6 +562,15 @@ agent_listtrusted (void *assuan_context) if (ti->flags.disabled) continue; bin2hex (ti->fpr, 20, key); + if (status_mode) + { + if (!add_to_strlist_try (&allhexgrips, key)) + { + err = gpg_error_from_syserror (); + goto leave; + } + continue; + } key[40] = ' '; key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*' : ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' '); @@ -555,8 +580,24 @@ agent_listtrusted (void *assuan_context) } } - unlock_trusttable (); - return 0; + if (status_mode) + { + unlock_trusttable (); + table_locked = 0; + + /* The back and forth converting of the fingerprint and all the + * locking and unlocking is somewhat clumsy but helps to re-use + * existing code. */ + for (sl = allhexgrips; sl; sl = sl->next) + if ((err = istrusted_internal (ctrl, sl->d, 1, NULL, 0))) + goto leave; + } + + leave: + if (table_locked) + unlock_trusttable (); + free_strlist (allhexgrips); + return err; } @@ -736,7 +777,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) insert a line break. The double percent sign is actually needed because it is also a printf format string. If you need to insert a plain % sign, you need to encode it as - "%%25". The second "%s" gets replaced by a hexdecimal + "%%25". The second "%s" gets replaced by a hexadecimal fingerprint string whereas the first one receives the name as stored in the certificate. */ L_("Please verify that the certificate identified as:%%0A" @@ -770,7 +811,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) sure that nobody else plays with our file and force a reread. */ lock_trusttable (); clear_trusttable (); - if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled) + if (!istrusted_internal (ctrl, fpr, 0, &is_disabled, 1) || is_disabled) { unlock_trusttable (); xfree (fprformatted); diff --git a/autogen.sh b/autogen.sh index 0abf10342..8695fcb51 100755 --- a/autogen.sh +++ b/autogen.sh @@ -15,7 +15,7 @@ # configure it for the respective package. It is maintained as part of # GnuPG and source copied by other packages. # -# Version: 2022-12-09 +# Version: 2025-03-10 configure_ac="configure.ac" @@ -72,9 +72,9 @@ FORCE= SILENT= PRINT_HOST=no PRINT_BUILD=no +PRINT_TSDIR=no tmp=$(dirname "$0") tsdir=$(cd "${tmp}"; pwd) -version_parts=3 if [ -n "${AUTOGEN_SH_SILENT}" ]; then SILENT=" --silent" @@ -85,9 +85,11 @@ if test x"$1" = x"--help"; then echo " --silent Silent operation" echo " --force Pass --force to autoconf" echo " --find-version Helper for configure.ac" - echo " --build-TYPE Configure to cross build for TYPE" + echo " --git-build Run all commands to build from a Git" echo " --print-host Print only the host triplet" echo " --print-build Print only the build platform triplet" + echo " --print-tsdir Print only the dir of this script" + echo " --build-TYPE Configure to cross build for TYPE" echo "" echo " ARGS are passed to configure in --build-TYPE mode." echo " Configuration for this script is expected in autogen.rc" @@ -135,11 +137,13 @@ die_p configure_opts= extraoptions= # List of optional variables sourced from autogen.rc and ~/.gnupg-autogen.rc +maintainer_mode_option= w32_toolprefixes= w32_extraoptions= w64_toolprefixes= w64_extraoptions= amd64_toolprefixes= +disable_gettext_checks= # End list of optional variables sourced from ~/.gnupg-autogen.rc # What follows are variables which are sourced but default to # environment variables or lacking them hardcoded values. @@ -156,6 +160,15 @@ case "$1" in SILENT=" --silent" shift ;; + --print-tsdir) + myhost="print-tsdir" + SILENT=" --silent" + shift + ;; + --git-build) + myhost="git-build" + shift + ;; --build-w32) myhost="w32" shift @@ -179,6 +192,25 @@ esac die_p +# **** GIT BUILD **** +# This is a helper to build from git. +if [ "$myhost" = "git-build" ]; then + tmp="$(pwd)" + cd "$tsdir" || fatal "error cd-ing to $tsdir" + ./autogen.sh || fatal "error running ./autogen.sh" + cd "$tmp" || fatal "error cd-ing back to $tmp" + die_p + "$tsdir"/configure || fatal "error running $tsdir/configure" + die_p + make || fatal "error running make" + die_p + make check || fatal "error running make check" + die_p + exit 0 +fi +# **** end GIT BUILD **** + + # Source our configuration if [ -f "${tsdir}/autogen.rc" ]; then . "${tsdir}/autogen.rc" @@ -190,6 +222,12 @@ if [ -f "$HOME/.gnupg-autogen.rc" ]; then . "$HOME/.gnupg-autogen.rc" fi +# Disable the --enable-maintainer_mode option. +if [ "${maintainer_mode_option}" = off ]; then + maintainer_mode_option= +elif [ -z "${maintainer_mode_option}" ]; then + maintainer_mode_option=--enable-maintainer-mode +fi # **** FIND VERSION **** # This is a helper for the configure.ac M4 magic @@ -207,47 +245,66 @@ if [ "$myhost" = "find-version" ]; then exit 1 fi - case "$version_parts" in - 2) - matchstr1="$package-$major.[0-9]*" - matchstr2="$package-$major-base" - vers="$major.$minor" - ;; - *) - matchstr1="$package-$major.$minor.[0-9]*" - matchstr2="$package-$major.$minor-base" - vers="$major.$minor.$micro" - ;; - esac + if [ -z "$micro" ]; then + matchstr1="$package-$major.[0-9]*" + matchstr2="$package-$major-base" + matchstr3="" + vers="$major.$minor" + else + matchstr1="$package-$major.$minor.[0-9]*" + matchstr2="$package-$major.[0-9]*-base" + matchstr3="$package-$major-base" + vers="$major.$minor.$micro" + fi + matchexcl="--exclude $package-*beta*" beta=no if [ -e .git ]; then ingit=yes - tmp=$(git describe --match "${matchstr1}" --long 2>/dev/null) - tmp=$(echo "$tmp" | sed s/^"$package"//) + tmp=$(git describe --match "${matchstr1}" $matchexcl --long 2>/dev/null) if [ -n "$tmp" ]; then - tmp=$(echo "$tmp" | sed s/^"$package"// \ - | awk -F- '$3!=0 && $3 !~ /^beta/ {print"-beta"$3}') + tmp=$(echo "$tmp" | sed s/^"$package"// \ + | awk -F- '$3!=0 && $3 !~ /^beta/ {print"-beta"$3}') else - tmp=$(git describe --match "${matchstr2}" --long 2>/dev/null \ - | awk -F- '$4!=0{print"-beta"$4}') + # (due tof "-base" in the tag we need to take the 4th field) + tmp=$(git describe --match "${matchstr2}" $matchexcl --long 2>/dev/null) + if [ -n "$tmp" ]; then + tmp=$(echo "$tmp" | sed s/^"$package"// \ + | awk -F- '$4!=0 && $4 !~ /^beta/ {print"-beta"$4}') + elif [ -n "${matchstr3}" ]; then + tmp=$(git describe --match "${matchstr3}" $matchexcl --long 2>/dev/null) + if [ -n "$tmp" ]; then + tmp=$(echo "$tmp" | sed s/^"$package"// \ + | awk -F- '$4!=0 && $4 !~ /^beta/ {print"-beta"$4}') + fi + fi fi [ -n "$tmp" ] && beta=yes + cid=$(git rev-parse --verify HEAD | tr -d '\n\r') rev=$(git rev-parse --short HEAD | tr -d '\n\r') rvd=$((0x$(echo ${rev} | dd bs=1 count=4 2>/dev/null))) else ingit=no beta=yes tmp="-unknown" + cid="0000000" rev="0000000" rvd="0" fi - echo "$package-$vers$tmp:$beta:$ingit:$vers$tmp:$vers:$tmp:$rev:$rvd:" + echo "$package-$vers$tmp:$beta:$ingit:$vers$tmp:$vers:$tmp:$rev:$rvd:$cid:" exit 0 fi # **** end FIND VERSION **** +# **** PRINT TSDIR VERSION **** +# This is a helper used by some configure.ac M4 magic +if [ "$myhost" = "print-tsdir" ]; then + echo "$tsdir" + exit 0 +fi +# **** end PRINT TSDIR **** + if [ ! -f "$tsdir/build-aux/config.guess" ]; then fatal "$tsdir/build-aux/config.guess not found" @@ -279,6 +336,7 @@ if [ "$myhost" = "w32" ]; then extraoptions="$extraoptions $w32_extraoptions" ;; esac + w32root=$(echo "$w32root" | sed s,^//,/,) info "Using $w32root as standard install directory" replace_sysroot @@ -311,7 +369,7 @@ if [ "$myhost" = "w32" ]; then fi fi - $tsdir/configure --enable-maintainer-mode ${SILENT} \ + $tsdir/configure "${maintainer_mode_option}" ${SILENT} \ --prefix=${w32root} \ --host=${host} --build=${build} SYSROOT=${w32root} \ PKG_CONFIG_LIBDIR=${w32root}/lib/pkgconfig \ @@ -356,7 +414,7 @@ if [ "$myhost" = "amd64" ]; then fi fi - $tsdir/configure --enable-maintainer-mode ${SILENT} \ + $tsdir/configure "${maintainer_mode_option}" ${SILENT} \ --prefix=${amd64root} \ --host=${host} --build=${build} \ ${configure_opts} ${extraoptions} "$@" @@ -379,17 +437,16 @@ q }' ${configure_ac}` automake_vers_num=`echo "$automake_vers" | cvtver` +gettext_vers="n/a" if [ -d "${tsdir}/po" ]; then gettext_vers=`sed -n '/^AM_GNU_GETTEXT_VERSION(/ { s/^.*\[\(.*\)])/\1/p q }' ${configure_ac}` gettext_vers_num=`echo "$gettext_vers" | cvtver` -else - gettext_vers="n/a" fi -if [ -z "$autoconf_vers" -o -z "$automake_vers" -o -z "$gettext_vers" ] +if [ -z "$autoconf_vers" -o -z "$automake_vers" ] then echo "**Error**: version information not found in "\`${configure_ac}\'"." >&2 exit 1 @@ -467,12 +524,21 @@ fi if [ -n "${ACLOCAL_FLAGS}" ]; then aclocal_flags="${aclocal_flags} ${ACLOCAL_FLAGS}" fi + +automake_flags="--gnu" +if [ -n "${extra_automake_flags}" ]; then + automake_flags="${automake_flags} ${extra_automake_flags}" +fi +if [ -n "${AUTOMAKE_FLAGS}" ]; then + automake_flags="${automake_flags} ${AUTOMAKE_FLAGS}" +fi + info "Running $ACLOCAL ${aclocal_flags} ..." $ACLOCAL ${aclocal_flags} info "Running autoheader..." $AUTOHEADER -info "Running automake --gnu ..." -$AUTOMAKE --gnu; +info "Running $AUTOMAKE ${automake_flags} ..." +$AUTOMAKE ${automake_flags}; info "Running autoconf${FORCE} ..." $AUTOCONF${FORCE} diff --git a/build-aux/getswdb.sh b/build-aux/getswdb.sh index 7d4b31eef..5feaf8612 100755 --- a/build-aux/getswdb.sh +++ b/build-aux/getswdb.sh @@ -28,15 +28,25 @@ cvtver () { usage() { cat <&2 ;; + *) + packages="$packages $1" + ;; esac shift done + # Mac OSX has only a shasum and not sha1sum if [ ${find_sha1sum} = yes ]; then for i in sha1sum shasum ; do @@ -114,16 +138,37 @@ if [ ${find_sha256sum} = yes ]; then fi +if [ $skip_verify = no ]; then + if [ ! -f "$distsigkey" ]; then + distsigkey="/usr/local/share/gnupg/distsigkey.gpg" + if [ ! -f "$distsigkey" ]; then + distsigkey="/usr/share/gnupg/distsigkey.gpg" + if [ ! -f "$distsigkey" ]; then + echo "no keyring with release keys found!" >&2 + exit 1 + fi + fi + echo "using release keys from $distsigkey" >&2 + skip_selfcheck=yes + fi +fi + + # Get GnuPG version from VERSION file. For a GIT checkout this means # that ./autogen.sh must have been run first. For a regular tarball # VERSION is always available. -if [ ! -f "$srcdir/../VERSION" ]; then +if [ $skip_selfcheck = no ]; then + if [ ! -f "$srcdir/../VERSION" ]; then echo "VERSION file missing - run autogen.sh first." >&2 exit 1 + fi + version=$(cat "$srcdir/../VERSION") +else + version="0.0.0" fi -version=$(cat "$srcdir/../VERSION") version_num=$(echo "$version" | cvtver) + if [ $skip_verify = no ]; then if ! $GPGV --version >/dev/null 2>/dev/null ; then echo "command \"gpgv\" is not installed" >&2 @@ -152,22 +197,22 @@ else exit 1 fi - if ! $WGET -q -O swdb.lst "$urlbase/swdb.lst" ; then + if ! $WGET ${WGETOPT} -q -O swdb.lst "$urlbase/swdb.lst" ; then echo "download of swdb.lst failed." >&2 exit 1 fi if [ $skip_verify = no ]; then - if ! $WGET -q -O swdb.lst.sig "$urlbase/swdb.lst.sig" ; then + if ! $WGET ${WGETOPT} -q -O swdb.lst.sig "$urlbase/swdb.lst.sig" ; then echo "download of swdb.lst.sig failed." >&2 exit 1 fi fi fi if [ $skip_verify = no ]; then - if ! $GPGV --keyring "$distsigkey" swdb.lst.sig swdb.lst; then + if ! $GPGV --keyring "$distsigkey" swdb.lst.sig swdb.lst 2>/dev/null; then echo "list of software versions is not valid!" >&2 exit 1 - fi + fi fi # @@ -188,3 +233,73 @@ if [ $skip_selfcheck = no ]; then exit 1 fi fi + + +# Download a package and check its signature. +download_pkg () { + local url="$1" + local file="${url##*/}" + + if ! $WGET ${WGETOPT} -q -O - "$url" >"${file}.tmp" ; then + echo "download of $file failed." >&2 + [ -f "${file}.tmp" ] && rm "${file}.tmp" + return 1 + fi + if [ $skip_verify = no ]; then + if ! $WGET ${WGETOPT} -q -O - "${url}.sig" >"${file}.tmpsig" ; then + echo "download of $file.sig failed." >&2 + [ -f "${file}.tmpsig" ] && rm "${file}.tmpsig" + return 1 + fi + if ! $GPGV -q --keyring "$distsigkey" \ + "${file}.tmpsig" "${file}.tmp" 2>/dev/null; then + echo "signature of $file is not valid!" >&2 + return 1 + fi + mv "${file}.tmpsig" "${file}.sig" + else + [ -f "${file}.sig" ] && rm "${file}.sig" + fi + mv "${file}.tmp" "${file}" + return 0 +} + + + +baseurl=$(awk '$1=="gpgorg_base" {print $2; exit 0}' swdb.lst) +for p in $packages; do + pver=$(awk '$1=="'"$p"'_ver" {print $2}' swdb.lst) + if [ -z "$pver" ]; then + echo "package '$p' not found" >&2 + die=yes + else + pdir=$(awk '$1=="'"$p"'_dir" {print $2":"$3":"$4}' swdb.lst) + if [ -n "$pdir" ]; then + psuf=$(echo "$pdir" | cut -d: -f3) + pname=$(echo "$pdir" | cut -d: -f2) + pdir=$(echo "$pdir" | cut -d: -f1) + else + psuf= + pdir="$p" + pname="$p" + fi + if [ -z "$psuf" ]; then + psuf=$(awk 'BEGIN {suf="bz2"}; + $1=="'"$p"'_sha1_gz" {suf="gz"; exit 0}; + $1=="'"$p"'_sha1_xz" {suf"xz"; exit 0}; + END {print suf}' swdb.lst) + fi + pfullname="$pname-$pver.tar.$psuf" + if [ $info_mode = yes ]; then + echo "$baseurl/$pdir/$pfullname" + else + echo "downloading $pfullname" + download_pkg "$baseurl/$pdir/$pfullname" || die=yes + fi + fi +done +if [ $die = yes ]; then + echo "errors found!" >&2 + exit 1 +fi +exit 0 diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk index db78afa50..99736c2b8 100644 --- a/build-aux/speedo.mk +++ b/build-aux/speedo.mk @@ -43,67 +43,21 @@ # # The information required to sign the tarballs and binaries # are expected in the developer specific file ~/.gnupg-autogen.rc". -# Here is an example: -#--8<---------------cut here---------------start------------->8--- -# # Location of the released tarball archives. Note that this is an -# # internal archive and before uploading this to the public server, -# # manual tests should be run and the git release tagged and pushed. -# # This is greped by the Makefile. -# RELEASE_ARCHIVE=foo@somehost:tarball-archive -# -# # The key used to sign the released sources. -# # This is greped by the Makefile. -# RELEASE_SIGNKEY=6DAA6E64A76D2840571B4902528897B826403ADA -# -# # For signing Windows binaries we need to employ a Windows machine. -# # We connect to this machine via ssh and take the connection -# # parameters via .ssh/config. For example a VM could be specified -# # like this: -# # -# # Host authenticode-signhost -# # HostName localhost -# # Port 27042 -# # User gpgsign -# # -# # Depending on the used token it might be necessary to allow single -# # signon and unlock the token before running the make. The following -# # variable references this entry. This is greped by the Makefile. -# AUTHENTICODE_SIGNHOST=authenticode-signhost -# -# # The name of the signtool as used on Windows. -# # This is greped by the Makefile. -# AUTHENTICODE_TOOL="C:\Program Files (x86)\Windows Kits\10\bin\signtool.exe" -# -# # To use osslsigncode the follwing entries are required and -# # an empty string must be given for AUTHENTICODE_SIGNHOST. -# # They are greped by the Makefile. -# AUTHENTICODE_KEY=/home/foo/.gnupg/my-authenticode-key.p12 -# AUTHENTICODE_CERTS=/home/foo/.gnupg/my-authenticode-certs.pem -# -# # If a smartcard is used for the Authenticode signature these -# # entries are required instead: -# AUTHENTICODE_KEY=card -# AUTHENTICODE_CERTS=/home/foo/.gnupg/my_authenticode_cert.pem -# OSSLSIGNCODE=/usr/bin/osslsigncode -# OSSLPKCS11ENGINE=/usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so -# SCUTEMODULE=/usr/local/lib/scute.so -# -#--8<---------------cut here---------------end--------------->8--- +# Use "gpg-authcode-sign.sh --template" to create a template. # We need to know our own name. SPEEDO_MK := $(realpath $(lastword $(MAKEFILE_LIST))) -.PHONY : help native native-gui w32-installer w32-source w32-wixlib -.PHONY : git-native git-native-gui git-w32-installer git-w32-source -.PHONY : this-native this-native-gui this-w32-installer this-w32-source +.PHONY : help native w32-installer w32-source w32-wixlib +.PHONY : git-native git-w32-installer git-w32-source +.PHONY : this-native this-w32-installer this-w32-source help: @echo 'usage: make -f speedo.mk TARGET' @echo ' with TARGET being one of:' @echo ' help This help' @echo ' native Native build of the GnuPG core' - @echo ' native-gui Ditto but with pinentry and GPA' @echo ' w32-installer Build a Windows installer' @echo ' w32-source Pack a source archive' @echo ' w32-release Build a Windows release' @@ -114,11 +68,12 @@ help: @echo 'Prepend TARGET with "git-" to build from GIT repos.' @echo 'Prepend TARGET with "this-" to build from the source tarball.' @echo 'Use STATIC=1 to build with statically linked libraries.' - @echo 'Use SELFCHECK=0 for a non-released version.' + @echo 'Use SELFCHECK=1 for additional check of the gnupg version.' @echo 'Use CUSTOM_SWDB=1 for an already downloaded swdb.lst.' @echo 'Use WIXPREFIX to provide the WIX binaries for the MSI package.' @echo ' Using WIX also requires wine with installed wine mono.' @echo ' See help-wixlib for more information' + @echo 'Set W32VERSION=w32 to build a 32 bit Windows version.' help-wixlib: @echo 'The buildsystem can create a wixlib to build MSI packages.' @@ -148,66 +103,52 @@ help-wixlib: SPEEDOMAKE := $(MAKE) -f $(SPEEDO_MK) UPD_SWDB=1 native: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=release WITH_GUI=0 all + $(SPEEDOMAKE) TARGETOS=native WHAT=release all git-native: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=git WITH_GUI=0 all + $(SPEEDOMAKE) TARGETOS=native WHAT=git all this-native: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=this WITH_GUI=0 all - -native-gui: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=release WITH_GUI=1 all - -git-native-gui: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=git WITH_GUI=1 all - -this-native-gui: check-tools - $(SPEEDOMAKE) TARGETOS=native WHAT=this WITH_GUI=1 all + $(SPEEDOMAKE) TARGETOS=native WHAT=this all w32-installer: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 installer + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release installer git-w32-installer: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=git WITH_GUI=0 installer + $(SPEEDOMAKE) TARGETOS=w32 WHAT=git installer this-w32-installer: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=this WITH_GUI=0 \ - CUSTOM_SWDB=1 installer + $(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 installer w32-wixlib: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 wixlib + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release wixlib git-w32-wixlib: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=git WITH_GUI=0 wixlib + $(SPEEDOMAKE) TARGETOS=w32 WHAT=git wixlib this-w32-wixlib: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=this WITH_GUI=0 \ - CUSTOM_SWDB=1 wixlib + $(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 wixlib w32-source: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 dist-source + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release dist-source git-w32-source: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=git WITH_GUI=0 dist-source + $(SPEEDOMAKE) TARGETOS=w32 WHAT=git dist-source this-w32-source: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=this WITH_GUI=0 \ - CUSTOM_SWDB=1 dist-source + $(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 dist-source w32-release: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \ - installer-from-source + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release installer-from-source w32-msi-release: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \ + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release \ WITH_WIXLIB=1 installer-from-source w32-sign-installer: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \ - sign-installer + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release sign-installer w32-release-offline: check-tools - $(SPEEDOMAKE) TARGETOS=w32 WHAT=release WITH_GUI=0 SELFCHECK=0 \ + $(SPEEDOMAKE) TARGETOS=w32 WHAT=release \ CUSTOM_SWDB=1 pkgrep=${HOME}/b pkg10rep=${HOME}/b \ installer-from-source @@ -217,11 +158,11 @@ w32-release-offline: check-tools # to "this" from the unpacked sources. WHAT=git -# Set target to "native" or "w32" +# Set target to "native" or "w32". TARGETOS= -# Set to 1 to build the GUI tools -WITH_GUI=0 +# To build a 32 bit Windows version also change this to "w32" +W32VERSION=w64 # Set to 1 to use a pre-installed swdb.lst instead of the online version. CUSTOM_SWDB=0 @@ -229,8 +170,8 @@ CUSTOM_SWDB=0 # Set to 1 to really download the swdb. UPD_SWDB=0 -# Set to 0 to skip the GnuPG version self-check -SELFCHECK=1 +# Set to 1 to run an additional GnuPG version check +SELFCHECK=0 # Set to 1 to build with statically linked libraries. STATIC=0 @@ -239,29 +180,36 @@ STATIC=0 # external packages. TARBALLS=$(shell pwd)/../tarballs -# Number of parallel make jobs for each package -MAKE_J=3 +# Check if nproc is available, set MAKE_J accordingly +MAKE_J = $(shell if command -v nproc >/dev/null 2>&1; then \ + nproc; else echo 6; \ + fi) + +# Extra options for wget(1) +WGETOPT= --retry-connrefused --retry-on-host-error + # Name to use for the w32 installer and sources + + INST_NAME=gnupg-w32 -# Use this to override the installaion directory for native builds. +# Use this to override the installation directory for native builds. INSTALL_PREFIX=none # Set this to the location of wixtools WIXPREFIX=$(shell readlink -f ~/w32root/wixtools) +# If patchelf(1) is not available disable the command. +PATCHELF := $(shell patchelf --version 2>/dev/null >/dev/null || echo "echo please run: ")patchelf + +# Set this to 1 to get verbose output +VERBOSE=0 + # Read signing information from ~/.gnupg-autogen.rc define READ_AUTOGEN_template -$(1) = $$(shell grep '^$(1)=' $$$$HOME/.gnupg-autogen.rc|cut -d= -f2) +$(1) = $$(shell grep '^[[:blank:]]*$(1)[[:blank:]]*=' $$$$HOME/.gnupg-autogen.rc|cut -d= -f2|xargs) endef -$(eval $(call READ_AUTOGEN_template,AUTHENTICODE_SIGNHOST)) -$(eval $(call READ_AUTOGEN_template,AUTHENTICODE_TOOL)) -$(eval $(call READ_AUTOGEN_template,AUTHENTICODE_KEY)) -$(eval $(call READ_AUTOGEN_template,AUTHENTICODE_CERTS)) -$(eval $(call READ_AUTOGEN_template,OSSLSIGNCODE)) -$(eval $(call READ_AUTOGEN_template,OSSLPKCS11ENGINE)) -$(eval $(call READ_AUTOGEN_template,SCUTEMODULE)) $(eval $(call READ_AUTOGEN_template,OVERRIDE_TARBALLS)) @@ -277,17 +225,17 @@ AUTHENTICODE_FILES= \ gpg-wks-client.exe \ gpg.exe \ gpgconf.exe \ - gpgme-w32spawn.exe \ gpgsm.exe \ gpgtar.exe \ gpgv.exe \ gpg-card.exe \ - libassuan-0.dll \ + keyboxd.exe \ + libassuan-9.dll \ libgcrypt-20.dll \ libgpg-error-0.dll \ - libgpgme-11.dll \ libksba-8.dll \ libnpth-0.dll \ + libntbtls-0.dll \ libsqlite3-0.dll \ pinentry-w32.exe \ scdaemon.exe \ @@ -318,60 +266,12 @@ w32src := $(topsrc)/build-aux/speedo/w32 # Fixme: Do we need to build pkg-config for cross-building? speedo_spkgs = \ - libgpg-error npth libgcrypt + libgpg-error npth libgcrypt \ + zlib bzip2 sqlite \ + libassuan libksba ntbtls gnupg ifeq ($(TARGETOS),w32) -speedo_spkgs += \ - zlib bzip2 sqlite -ifeq ($(WITH_GUI),1) -speedo_spkgs += gettext libiconv -endif -endif - -speedo_spkgs += \ - libassuan libksba - -ifeq ($(TARGETOS),w32) -speedo_spkgs += \ - ntbtls -endif - -speedo_spkgs += \ - gnupg - -ifeq ($(TARGETOS),w32) -ifeq ($(WITH_GUI),1) -speedo_spkgs += \ - libffi glib pkg-config -endif -endif - -ifeq ($(STATIC),0) -speedo_spkgs += \ - gpgme -endif - -ifeq ($(TARGETOS),w32) -ifeq ($(WITH_GUI),1) -speedo_spkgs += \ - libpng \ - gdk-pixbuf atk pixman cairo pango gtk+ -endif -endif - -ifeq ($(TARGETOS),w32) - speedo_spkgs += pinentry -ifeq ($(WITH_GUI),1) -speedo_spkgs += gpa gpgex -endif - -else - -ifeq ($(WITH_GUI),1) -speedo_spkgs += pinentry gpa -endif - endif @@ -381,16 +281,18 @@ endif # Packages which are additionally build for 64 bit Windows. They are # only used for gpgex and thus we need to build them only if we want # a full installer. -speedo_w64_spkgs = -ifeq ($(WITH_GUI),1) -speedo_w64_spkgs += libgpg-error libiconv gettext libassuan gpgex +ifeq ($(W32VERSION),w64) + # Keep this empty + speedo_w64_spkgs = +else + speedo_w64_spkgs = endif # Packages which use the gnupg autogen.sh build style speedo_gnupg_style = \ libgpg-error npth libgcrypt \ - libassuan libksba ntbtls gnupg gpgme \ - pinentry gpa gpgex + libassuan libksba ntbtls gnupg \ + pinentry # Packages which use only make and no build directory speedo_make_only_style = \ @@ -405,6 +307,7 @@ endif ifeq ($(SELFCHECK),0) getswdb_options += --skip-selfcheck endif +getswdb_options += --wgetopt="$(WGETOPT)" ifeq ($(UPD_SWDB),1) SWDB := $(shell $(topsrc)/build-aux/getswdb.sh $(getswdb_options) && echo okay) ifeq ($(strip $(SWDB)),) @@ -416,7 +319,7 @@ endif # Version numbers of the released packages gnupg_ver_this = $(shell cat $(topsrc)/VERSION) -gnupg_ver := $(shell awk '$$1=="gnupg24_ver" {print $$2}' swdb.lst) +gnupg_ver := $(shell awk '$$1=="gnupg26_ver" {print $$2}' swdb.lst) libgpg_error_ver := $(shell awk '$$1=="libgpg_error_ver" {print $$2}' swdb.lst) libgpg_error_sha1:= $(shell awk '$$1=="libgpg_error_sha1" {print $$2}' swdb.lst) @@ -442,22 +345,10 @@ ntbtls_ver := $(shell awk '$$1=="ntbtls_ver" {print $$2}' swdb.lst) ntbtls_sha1 := $(shell awk '$$1=="ntbtls_sha1" {print $$2}' swdb.lst) ntbtls_sha2 := $(shell awk '$$1=="ntbtls_sha2" {print $$2}' swdb.lst) -gpgme_ver := $(shell awk '$$1=="gpgme_ver" {print $$2}' swdb.lst) -gpgme_sha1 := $(shell awk '$$1=="gpgme_sha1" {print $$2}' swdb.lst) -gpgme_sha2 := $(shell awk '$$1=="gpgme_sha2" {print $$2}' swdb.lst) - pinentry_ver := $(shell awk '$$1=="pinentry_ver" {print $$2}' swdb.lst) pinentry_sha1 := $(shell awk '$$1=="pinentry_sha1" {print $$2}' swdb.lst) pinentry_sha2 := $(shell awk '$$1=="pinentry_sha2" {print $$2}' swdb.lst) -gpa_ver := $(shell awk '$$1=="gpa_ver" {print $$2}' swdb.lst) -gpa_sha1 := $(shell awk '$$1=="gpa_sha1" {print $$2}' swdb.lst) -gpa_sha2 := $(shell awk '$$1=="gpa_sha2" {print $$2}' swdb.lst) - -gpgex_ver := $(shell awk '$$1=="gpgex_ver" {print $$2}' swdb.lst) -gpgex_sha1 := $(shell awk '$$1=="gpgex_sha1" {print $$2}' swdb.lst) -gpgex_sha2 := $(shell awk '$$1=="gpgex_sha2" {print $$2}' swdb.lst) - zlib_ver := $(shell awk '$$1=="zlib_ver" {print $$2}' swdb.lst) zlib_sha1 := $(shell awk '$$1=="zlib_sha1_gz" {print $$2}' swdb.lst) zlib_sha2 := $(shell awk '$$1=="zlib_sha2_gz" {print $$2}' swdb.lst) @@ -471,9 +362,9 @@ sqlite_sha1 := $(shell awk '$$1=="sqlite_sha1_gz" {print $$2}' swdb.lst) sqlite_sha2 := $(shell awk '$$1=="sqlite_sha2_gz" {print $$2}' swdb.lst) -$(info Information from the version database) +$(info Information from the version database:) $(info GnuPG ..........: $(gnupg_ver) (building $(gnupg_ver_this))) -$(info Libgpg-error ...: $(libgpg_error_ver)) +$(info GpgRT ..........: $(libgpg_error_ver)) $(info Npth ...........: $(npth_ver)) $(info Libgcrypt ......: $(libgcrypt_ver)) $(info Libassuan ......: $(libassuan_ver)) @@ -482,33 +373,37 @@ $(info Zlib ...........: $(zlib_ver)) $(info Bzip2 ..........: $(bzip2_ver)) $(info SQLite .........: $(sqlite_ver)) $(info NtbTLS .. ......: $(ntbtls_ver)) -$(info GPGME ..........: $(gpgme_ver)) $(info Pinentry .......: $(pinentry_ver)) -$(info GPA ............: $(gpa_ver)) -$(info GpgEX.... ......: $(gpgex_ver)) endif +$(info Information for this run:) +$(info Build type .....: $(WHAT)) +$(info Target .........: $(TARGETOS)) +ifeq ($(TARGETOS),w32) +ifeq ($(W32VERSION),w64) + $(info Windows version : 64 bit) +else + $(info Windows version : 32 bit) +ifneq ($(W32VERSION),w32) + $(error W32VERSION is not set to a proper value: Use only w32 or w64) +endif +endif +endif + + # Version number for external packages pkg_config_ver = 0.23 libiconv_ver = 1.14 gettext_ver = 0.18.2.1 -libffi_ver = 3.0.13 -glib_ver = 2.34.3 -libpng_ver = 1.4.12 -gdk_pixbuf_ver = 2.26.5 -atk_ver = 1.32.0 -pango_ver = 1.29.4 -pixman_ver = 0.32.4 -cairo_ver = 1.12.16 -gtk__ver = 2.24.17 -# The GIT repository. Using a local repo is much faster. -#gitrep = git://git.gnupg.org + +# The GIT repository. Using a local repo is much faster and more secure. +# The default is to expect it below ~/s/ gitrep = ${HOME}/s # The tarball directories pkgrep = https://gnupg.org/ftp/gcrypt -pkg10rep = ftp://ftp.g10code.com/g10code +pkg10rep = foo://no-default-repo.local pkg2rep = $(TARBALLS) # For each package, the following variables can be defined: @@ -519,10 +414,11 @@ pkg2rep = $(TARBALLS) # speedo_pkg_PACKAGE_tar: URL to the tar file that should be built. # # Exactly one of the above variables is required. Note that this -# version of speedo does not cache repositories or tar files, and does -# not test the integrity of the downloaded software. If you care -# about this, you can also specify filenames to locally verified files. -# Filenames are differentiated from URLs by starting with a slash '/'. +# version of speedo does not cache repositories or tar files. The +# integrity of the downloaded software is checked using the SWDB. +# Note that you you can also specify filenames to already downloaded +# files. Filenames are differentiated from URLs by testing whether +# the character is a slash ('/'). # # speedo_pkg_PACKAGE_configure: Extra arguments to configure. # @@ -547,14 +443,8 @@ else ifeq ($(WHAT),git) speedo_pkg_libksba_gitref = master speedo_pkg_ntbtls_git = $(gitrep)/ntbtls speedo_pkg_ntbtls_gitref = master - speedo_pkg_gpgme_git = $(gitrep)/gpgme - speedo_pkg_gpgme_gitref = master speedo_pkg_pinentry_git = $(gitrep)/pinentry speedo_pkg_pinentry_gitref = master - speedo_pkg_gpa_git = $(gitrep)/gpa - speedo_pkg_gpa_gitref = master - speedo_pkg_gpgex_git = $(gitrep)/gpgex - speedo_pkg_gpgex_gitref = master else ifeq ($(WHAT),release) speedo_pkg_libgpg_error_tar = \ $(pkgrep)/libgpg-error/libgpg-error-$(libgpg_error_ver).tar.bz2 @@ -568,14 +458,8 @@ else ifeq ($(WHAT),release) $(pkgrep)/libksba/libksba-$(libksba_ver).tar.bz2 speedo_pkg_ntbtls_tar = \ $(pkgrep)/ntbtls/ntbtls-$(ntbtls_ver).tar.bz2 - speedo_pkg_gpgme_tar = \ - $(pkgrep)/gpgme/gpgme-$(gpgme_ver).tar.bz2 speedo_pkg_pinentry_tar = \ $(pkgrep)/pinentry/pinentry-$(pinentry_ver).tar.bz2 - speedo_pkg_gpa_tar = \ - $(pkgrep)/gpa/gpa-$(gpa_ver).tar.bz2 - speedo_pkg_gpgex_tar = \ - $(pkg10rep)/gpgex/gpgex-$(gpgex_ver).tar.bz2 else $(error invalid value for WHAT (use on of: git release this)) endif @@ -586,15 +470,6 @@ speedo_pkg_bzip2_tar = $(pkgrep)/bzip2/bzip2-$(bzip2_ver).tar.gz speedo_pkg_sqlite_tar = $(pkgrep)/sqlite/sqlite-autoconf-$(sqlite_ver).tar.gz speedo_pkg_libiconv_tar = $(pkg2rep)/libiconv-$(libiconv_ver).tar.gz speedo_pkg_gettext_tar = $(pkg2rep)/gettext-$(gettext_ver).tar.gz -speedo_pkg_libffi_tar = $(pkg2rep)/libffi-$(libffi_ver).tar.gz -speedo_pkg_glib_tar = $(pkg2rep)/glib-$(glib_ver).tar.xz -speedo_pkg_libpng_tar = $(pkg2rep)/libpng-$(libpng_ver).tar.bz2 -speedo_pkg_gdk_pixbuf_tar = $(pkg2rep)/gdk-pixbuf-$(gdk_pixbuf_ver).tar.xz -speedo_pkg_atk_tar = $(pkg2rep)/atk-$(atk_ver).tar.bz2 -speedo_pkg_pango_tar = $(pkg2rep)/pango-$(pango_ver).tar.bz2 -speedo_pkg_pixman_tar = $(pkg2rep)/pixman-$(pixman_ver).tar.gz -speedo_pkg_cairo_tar = $(pkg2rep)/cairo-$(cairo_ver).tar.xz -speedo_pkg_gtk__tar = $(pkg2rep)/gtk+-$(gtk__ver).tar.xz # @@ -603,8 +478,10 @@ speedo_pkg_gtk__tar = $(pkg2rep)/gtk+-$(gtk__ver).tar.xz speedo_pkg_npth_configure = --enable-static -speedo_pkg_libgpg_error_configure = --enable-static --enable-install-gpg-error-config -speedo_pkg_w64_libgpg_error_configure = --enable-static --enable-install-gpg-error-config +speedo_pkg_libgpg_error_configure = --enable-static +speedo_pkg_w64_libgpg_error_configure = --enable-static +speedo_pkg_libgpg_error_extracflags = -D_WIN32_WINNT=0x0600 +speedo_pkg_w64_libgpg_error_extracflags = -D_WIN32_WINNT=0x0600 speedo_pkg_libassuan_configure = --enable-static speedo_pkg_w64_libassuan_configure = --enable-static @@ -613,9 +490,6 @@ speedo_pkg_libgcrypt_configure = --disable-static speedo_pkg_libksba_configure = --disable-static -speedo_pkg_ntbtls_configure = --enable-static - - ifeq ($(STATIC),1) speedo_pkg_npth_configure += --disable-shared @@ -628,16 +502,13 @@ speedo_pkg_libgcrypt_configure += --disable-shared speedo_pkg_libksba_configure += --disable-shared endif -# For now we build ntbtls only static -speedo_pkg_ntbtls_configure = --disable-shared - ifeq ($(TARGETOS),w32) speedo_pkg_gnupg_configure = \ --disable-g13 --enable-ntbtls --disable-tpm2d else speedo_pkg_gnupg_configure = --disable-g13 --enable-wks-tools endif -speedo_pkg_gnupg_extracflags = -g +speedo_pkg_gnupg_extracflags = # Create the version info files only for W32 so that they won't get # installed if for example INSTALL_PREFIX=/usr/local is used. @@ -650,25 +521,6 @@ define speedo_pkg_gnupg_post_install endef endif -# The LDFLAGS is needed for -lintl for glib. -ifeq ($(WITH_GUI),1) -speedo_pkg_gpgme_configure = \ - --enable-static --enable-w32-glib \ - --with-gpg-error-prefix=$(idir) \ - LDFLAGS=-L$(idir)/lib -else -speedo_pkg_gpgme_configure = \ - --disable-static --disable-w32-glib \ - --with-gpg-error-prefix=$(idir) \ - LDFLAGS=-L$(idir)/lib -endif - - -ifeq ($(TARGETOS),w32) -speedo_pkg_pinentry_configure = --disable-pinentry-gtk2 -else -speedo_pkg_pinentry_configure = --enable-pinentry-gtk2 -endif speedo_pkg_pinentry_configure += \ --disable-pinentry-qt5 \ --disable-pinentry-qt \ @@ -679,22 +531,6 @@ speedo_pkg_pinentry_configure += \ CXXFLAGS=-static-libstdc++ -speedo_pkg_gpa_configure = \ - --with-libiconv-prefix=$(idir) --with-libintl-prefix=$(idir) \ - --with-gpgme-prefix=$(idir) --with-zlib=$(idir) \ - --with-libassuan-prefix=$(idir) --with-gpg-error-prefix=$(idir) - -speedo_pkg_gpgex_configure = \ - --with-gpg-error-prefix=$(idir) \ - --with-libassuan-prefix=$(idir) \ - --enable-gpa-only - -speedo_pkg_w64_gpgex_configure = \ - --with-gpg-error-prefix=$(idir6) \ - --with-libassuan-prefix=$(idir6) \ - --enable-gpa-only - - # # External packages # @@ -741,6 +577,9 @@ speedo_pkg_bzip2_make_args = \ speedo_pkg_bzip2_make_args_inst = \ PREFIX=$(idir) CC="$(host)-gcc" AR="$(host)-ar" RANLIB="$(host)-ranlib" +else +speedo_pkg_bzip2_make_args_inst = \ + PREFIX=$(idir) endif speedo_pkg_w64_libiconv_configure = \ @@ -758,74 +597,31 @@ speedo_pkg_gettext_extracflags = -O2 speedo_pkg_gettext_make_dir = gettext-runtime -speedo_pkg_glib_configure = \ - --disable-modular-tests \ - --with-libiconv=gnu \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib \ - CCC=$(host)-g++ \ - LIBFFI_CFLAGS=-I$(idir)/lib/libffi-$(libffi_ver)/include \ - LIBFFI_LIBS=\"-L$(idir)/lib -lffi\" -ifeq ($(TARGETOS),w32) -speedo_pkg_glib_extracflags = -march=i486 -endif - -ifeq ($(TARGETOS),w32) -speedo_pkg_libpng_configure = \ - CPPFLAGS=\"-I$(idir)/include -DPNG_BUILD_DLL\" \ - LDFLAGS=\"-L$(idir)/lib\" LIBPNG_DEFINES=\"-DPNG_BUILD_DLL\" -else -speedo_pkg_libpng_configure = \ - CPPFLAGS=\"-I$(idir)/include\" \ - LDFLAGS=\"-L$(idir)/lib\" -endif - -ifneq ($(TARGETOS),w32) -speedo_pkg_gdk_pixbuf_configure = --without-libtiff --without-libjpeg -endif - -speedo_pkg_pixman_configure = \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib - -ifeq ($(TARGETOS),w32) -speedo_pkg_cairo_configure = \ - --disable-qt --disable-ft --disable-fc \ - --enable-win32 --enable-win32-font \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib -else -speedo_pkg_cairo_configure = \ - --disable-qt \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib -endif - -speedo_pkg_pango_configure = \ - --disable-gtk-doc \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib - -speedo_pkg_gtk__configure = \ - --disable-cups \ - CPPFLAGS=-I$(idir)/include \ - LDFLAGS=-L$(idir)/lib - - # --------- all: all-speedo +install: install-speedo + report: report-speedo clean: clean-speedo + +ifeq ($(W32VERSION),w64) +W32CC_PREFIX = x86_64 +else +W32CC_PREFIX = i686 +endif + ifeq ($(TARGETOS),w32) -STRIP = i686-w64-mingw32-strip +STRIP = $(W32CC_PREFIX)-w64-mingw32-strip +W32STRIP32 = i686-w64-mingw32-strip else STRIP = strip endif -W32CC = i686-w64-mingw32-gcc +W32CC = $(W32CC_PREFIX)-w64-mingw32-gcc +W32CC32 = i686-w64-mingw32-gcc -include config.mk @@ -864,16 +660,27 @@ speedo_w64_build_list = $(speedo_w64_spkgs) # assignments), we check that the targetos has been given ifneq ($(TARGETOS),) +# Check for VERBOSE variable to conditionally set the silent option +ifeq ($(VERBOSE),1) + slient_flag = + autogen_sh_silent_flag = +else + slient_flag = --silent + autogen_sh_silent_flag = AUTOGEN_SH_SILENT=1 +endif + # Determine build and host system -build := $(shell $(topsrc)/autogen.sh --silent --print-build) +build := $(shell $(topsrc)/autogen.sh $(silent_flag) --print-build) ifeq ($(TARGETOS),w32) - speedo_autogen_buildopt := --build-w32 + speedo_autogen_buildopt := --build-$(W32VERSION) speedo_autogen_buildopt6 := --build-w64 - host := $(shell $(topsrc)/autogen.sh --silent --print-host --build-w32) - host6:= $(shell $(topsrc)/autogen.sh --silent --print-host --build-w64) + host := $(shell $(topsrc)/autogen.sh $(silent_flag) --print-host \ + --build-$(W32VERSION)) + host6:= $(shell $(topsrc)/autogen.sh $(silent_flag) --print-host \ + --build-w64) speedo_host_build_option := --host=$(host) --build=$(build) speedo_host_build_option6 := --host=$(host6) --build=$(build) - speedo_w32_cflags := -mms-bitfields + speedo_w32_cflags := -fcf-protection=full else speedo_autogen_buildopt := host := @@ -934,6 +741,9 @@ define SETVARS fi; \ pkgbdir="$(bdir)/$(1)"; \ pkgcfg="$(call GETVAR,speedo_pkg_$(1)_configure)"; \ + if [ "$(TARGETOS)" != native ]; then \ + pkgcfg="$(pkgcfg) --libdir=$(idir)/lib"; \ + fi; \ tmp="$(speedo_w32_cflags) \ $(call GETVAR,speedo_pkg_$(1)_extracflags)"; \ if [ x$$$$(echo "$$$$tmp" | tr -d '[:space:]')x != xx ]; then \ @@ -1038,7 +848,7 @@ $(stampdir)/stamp-$(1)-00-unpack: $(stampdir)/stamp-directories [ -f tmp.tgz ] && rm tmp.tgz; \ case "$$$${tar}" in \ /*) $$$${pretar} < $$$${tar} | tar xf - ;; \ - *) wget -q -O - $$$${tar} | tee tmp.tgz \ + *) wget $(WGETOPT) -q -O - $$$${tar} | tee tmp.tgz \ | $$$${pretar} | tar x$$$${opt}f - ;; \ esac; \ if [ -f tmp.tgz ]; then \ @@ -1093,13 +903,14 @@ else ifneq ($(findstring $(1),$(speedo_gnupg_style)),) mkdir "$$$${pkgbdir}"; \ cd "$$$${pkgbdir}"; \ if [ -n "$(speedo_autogen_buildopt)" ]; then \ - eval AUTOGEN_SH_SILENT=1 w32root="$(idir)" \ + eval $(autogen_sh_silent_flag) \ + $(W32VERSION)root="$(idir)" \ "$$$${pkgsdir}/autogen.sh" \ $(speedo_autogen_buildopt) \ $$$${pkgcfg} $$$${pkgextracflags}; \ else \ eval "$$$${pkgsdir}/configure" \ - --silent \ + $(silent_flag) \ --enable-maintainer-mode \ --prefix="$(idir)" \ $$$${pkgcfg} $$$${pkgextracflags}; \ @@ -1109,7 +920,7 @@ else mkdir "$$$${pkgbdir}"; \ cd "$$$${pkgbdir}"; \ eval "$$$${pkgsdir}/configure" \ - --silent $(speedo_host_build_option) \ + $(silent_flag) $(speedo_host_build_option) \ --prefix="$(idir)" \ $$$${pkgcfg} $$$${pkgextracflags}; \ ) @@ -1128,13 +939,13 @@ else ifneq ($(findstring $(1),$(speedo_gnupg_style)),) mkdir "$$$${pkgbdir}"; \ cd "$$$${pkgbdir}"; \ if [ -n "$(speedo_autogen_buildopt)" ]; then \ - eval AUTOGEN_SH_SILENT=1 w64root="$(idir6)" \ + eval $(autogen_sh_silent_flag) w64root="$(idir6)" \ "$$$${pkgsdir}/autogen.sh" \ $(speedo_autogen_buildopt6) \ $$$${pkgcfg} $$$${pkgextracflags}; \ else \ eval "$$$${pkgsdir}/configure" \ - --silent \ + $(silent_flag) \ --enable-maintainer-mode \ --prefix="$(idir6)" \ $$$${pkgcfg} $$$${pkgextracflags}; \ @@ -1144,7 +955,7 @@ else mkdir "$$$${pkgbdir}"; \ cd "$$$${pkgbdir}"; \ eval "$$$${pkgsdir}/configure" \ - --silent $(speedo_host_build_option6) \ + $(silent_flag) $(speedo_host_build_option6) \ --prefix="$(idir6)" \ $$$${pkgcfg} $$$${pkgextracflags}; \ ) @@ -1285,6 +1096,71 @@ clean-pkg-versions: @: >$(bdir)/pkg-versions.txt all-speedo: $(stampdir)/stamp-final +ifneq ($(TARGETOS),w32) + @(set -e;\ + cd "$(idir)"; \ + echo "speedo: Making RPATH relative";\ + for d in bin sbin libexec lib; do \ + for f in $$(find $$d -type f); do \ + if file $$f | grep ELF >/dev/null; then \ + $(PATCHELF) --set-rpath '$$ORIGIN/../lib' $$f; \ + fi; \ + done; \ + done; \ + echo "sysconfdir = /etc/gnupg" >bin/gpgconf.ctl ;\ + echo "rootdir = $(idir)" >>bin/gpgconf.ctl ;\ + echo "speedo: /*" ;\ + echo "speedo: * Now run for example:" ;\ + echo "speedo: * make -f $(topsrc)/build-aux/speedo.mk install SYSROOT=/usr/local/gnupg26" ;\ + echo "speedo: * This copies copy $(idir)/ to the final location and" ;\ + echo "speedo: * adjusts $(idir)/bin/gpgconf.ctl accordingly" ;\ + echo "speedo: */") +endif + +# No dependencies for the install target; instead we test whether +# some of the to be installed files are available. This avoids +# accidental rebuilds under a wrong account. +install-speedo: +ifneq ($(TARGETOS),w32) + @(set -e; \ + cd "$(idir)"; \ + if [ x"$$SYSROOT" = x ]; then \ + echo "speedo: ERROR: SYSROOT has not been given";\ + echo "speedo: Set SYSROOT to the desired install directory";\ + echo "speedo: Example:";\ + echo "speedo: make -f $(topsrc)/build-aux/speedo.mk install SYSROOT=/usr/local/gnupg26";\ + exit 1;\ + fi;\ + if [ ! -d "$$SYSROOT"/bin ]; then if ! mkdir "$$SYSROOT"/bin; then \ + echo "speedo: error creating target directory";\ + exit 1;\ + fi; fi;\ + if ! touch "$$SYSROOT"/bin/gpgconf.ctl; then \ + echo "speedo: Error writing $$SYSROOT/bin/gpgconf.ctl";\ + echo "speedo: Please check the permissions";\ + exit 1;\ + fi;\ + if [ ! -f bin/gpgconf.ctl ]; then \ + echo "speedo: ERROR: Nothing to install";\ + echo "speedo: Please run a build first";\ + echo "speedo: Example:";\ + echo "speedo: make -f build-aux/speedo.mk native";\ + exit 1;\ + fi;\ + echo "speedo: Installing files to $$SYSROOT";\ + find . -type f -executable \ + -exec install -Dm 755 "{}" "$$SYSROOT/{}" \; ;\ + find . -type l -executable \ + -exec install -Dm 755 "{}" "$$SYSROOT/{}" \; ;\ + find . -type f \! -executable \ + -exec install -Dm 644 "{}" "$$SYSROOT/{}" \; ;\ + echo "sysconfdir = /etc/gnupg" > "$$SYSROOT"/bin/gpgconf.ctl ;\ + echo "rootdir = $$SYSROOT" >> "$$SYSROOT"/bin/gpgconf.ctl ;\ + echo '/*' ;\ + echo " * Installation to $$SYSROOT done" ;\ + echo ' */' ) +endif + report-speedo: $(addprefix report-,$(speedo_build_list)) @@ -1302,6 +1178,9 @@ clean-speedo: # {{{ ifeq ($(TARGETOS),w32) +# The exclude '*.[ao]' takes care of the zlib and bzip2 peculiarity +# which keeps the build files in the source directory. See also the +# speedo_make_only_style macro. dist-source: installer for i in 00 01 02 03; do sleep 1;touch PLAY/stamps/stamp-*-${i}-*;done (set -e;\ @@ -1312,6 +1191,7 @@ dist-source: installer --anchored --exclude './PLAY' . ;\ tar --totals -rf "$$tarname" --exclude-backups --exclude-vcs \ --transform='s,^,$(INST_NAME)-$(INST_VERSION)/,' \ + --exclude='*.[ao]' \ PLAY/stamps/stamp-*-00-unpack PLAY/src swdb.lst swdb.lst.sig ;\ [ -f "$$tarname".xz ] && rm "$$tarname".xz;\ xz -T0 "$$tarname" ;\ @@ -1342,13 +1222,13 @@ $(bdir)/README.txt: $(bdir)/NEWS.tmp $(topsrc)/README $(w32src)/README.txt \ $(bdir)/g4wihelp.dll: $(w32src)/g4wihelp.c $(w32src)/exdll.h $(w32src)/exdll.c (set -e; cd $(bdir); \ - $(W32CC) -DUNICODE -static-libgcc -I . -O2 -c \ + $(W32CC32) -DUNICODE -static-libgcc -I . -O2 -c \ -o exdll.o $(w32src)/exdll.c; \ - $(W32CC) -DUNICODE -static-libgcc -I. -shared -O2 \ + $(W32CC32) -DUNICODE -static-libgcc -I. -shared -O2 \ -o g4wihelp.dll $(w32src)/g4wihelp.c exdll.o \ -lwinmm -lgdi32 -luserenv \ -lshell32 -loleaut32 -lshlwapi -lmsimg32; \ - $(STRIP) g4wihelp.dll) + $(W32STRIP32) g4wihelp.dll) w32_insthelpers: $(bdir)/g4wihelp.dll @@ -1356,9 +1236,6 @@ $(bdir)/inst-options.ini: $(w32src)/inst-options.ini cat $(w32src)/inst-options.ini >$(bdir)/inst-options.ini extra_installer_options = -ifeq ($(WITH_GUI),1) -extra_installer_options += -DWITH_GUI=1 -endif # Note that we sign only when doing the final installer. installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt @@ -1447,7 +1324,7 @@ wixlib: installer $(bdir)/README.txt $(w32src)/wixlib.wxs ) define MKSWDB_commands - ( pref="#+macro: gnupg24_w32_$(3)" ;\ + ( pref="#+macro: gnupg26_w32_$(3)" ;\ echo "$${pref}ver $(INST_VERSION)_$(BUILD_DATESTR)" ;\ echo "$${pref}date $(2)" ;\ echo "$${pref}size $$(wc -c <$(1)|awk '{print int($$1/1024)}')k";\ @@ -1458,35 +1335,13 @@ endef # Sign the file $1 and save the result as $2 define AUTHENTICODE_sign - set -e;\ - if [ -n "$(AUTHENTICODE_SIGNHOST)" ]; then \ - echo "speedo: Signing via host $(AUTHENTICODE_SIGNHOST)";\ - scp $(1) "$(AUTHENTICODE_SIGNHOST):a.exe" ;\ - ssh "$(AUTHENTICODE_SIGNHOST)" '$(AUTHENTICODE_TOOL)' sign \ - /a /n '"g10 Code GmbH"' \ - /tr 'http://rfc3161timestamp.globalsign.com/advanced' /td sha256 \ - /fd sha256 /du https://gnupg.org a.exe ;\ - scp "$(AUTHENTICODE_SIGNHOST):a.exe" $(2);\ - echo "speedo: signed file is '$(2)'" ;\ - elif [ "$(AUTHENTICODE_KEY)" = card ]; then \ - echo "speedo: Signing using a card: '$(1)'";\ - $(OSSLSIGNCODE) sign \ - -pkcs11engine $(OSSLPKCS11ENGINE) \ - -pkcs11module $(SCUTEMODULE) \ - -certs $(AUTHENTICODE_CERTS) \ - -h sha256 -n GnuPG -i https://gnupg.org \ - -ts http://rfc3161timestamp.globalsign.com/advanced \ - -in $(1) -out $(2).tmp ; mv $(2).tmp $(2) ; \ - elif [ -e "$(AUTHENTICODE_KEY)" ]; then \ - echo "speedo: Signing using key $(AUTHENTICODE_KEY)";\ - osslsigncode sign -certs $(AUTHENTICODE_CERTS) \ - -pkcs12 $(AUTHENTICODE_KEY) -askpass \ - -ts "http://timestamp.globalsign.com/scripts/timstamp.dll" \ - -h sha256 -n GnuPG -i https://gnupg.org \ - -in $(1) -out $(2) ;\ + (set -e; \ + if (gpg-authcode-sign.sh --version >/dev/null); then \ + gpg-authcode-sign.sh "$(1)" "$(2)"; \ else \ - echo "speedo: WARNING: Binaries are not signed"; \ - fi + echo 2>&1 "warning: Please install gpg-authcode-sign.sh to sign files." ;\ + [ "$(1)" != "$(2)" ] && cp "$(1)" "$(2)" ;\ + fi) endef # Help target for testing to sign a file. @@ -1543,10 +1398,8 @@ sign-installer: if [ -f "$${msifile}" ]; then \ $(call MKSWDB_commands,$${msifile},$${reldate},"wixlib_"); \ fi; \ - echo "speedo: /*" ;\ - echo "speedo: * Verification result" ;\ - echo "speedo: */" ;\ - osslsigncode verify $${exefile} \ + echo "speedo: /* (osslsigncode verify disabled) */" ;\ + echo osslsigncode verify $${exefile} \ ) @@ -1556,7 +1409,7 @@ endif # -# Check availibility of standard tools and prepare everything. +# Check availability of standard tools and prepare everything. # check-tools: $(stampdir)/stamp-directories @@ -1566,4 +1419,4 @@ check-tools: $(stampdir)/stamp-directories # Mark phony targets # .PHONY: all all-speedo report-speedo clean-stamps clean-speedo installer \ - w32_insthelpers check-tools clean-pkg-versions + w32_insthelpers check-tools clean-pkg-versions install-speedo install diff --git a/build-aux/speedo/w32/README.txt b/build-aux/speedo/w32/README.txt index 7c2909507..df7b3a807 100644 --- a/build-aux/speedo/w32/README.txt +++ b/build-aux/speedo/w32/README.txt @@ -60,7 +60,7 @@ Below is the README file as distributed with the GnuPG source. 4. Software Versions of the Included Packages ============================================= -GnuPG for Windows depends on several independet developed packages +GnuPG for Windows depends on several independent developed packages which are part of the installation. These packages along with their version numbers and the SHA-1 checksums of their compressed tarballs are listed here: diff --git a/build-aux/speedo/w32/g4wihelp.c b/build-aux/speedo/w32/g4wihelp.c index bae4b837c..33d186c40 100644 --- a/build-aux/speedo/w32/g4wihelp.c +++ b/build-aux/speedo/w32/g4wihelp.c @@ -24,7 +24,7 @@ ************************************************************ * The code for the splash screen has been taken from the Splash * plugin of the NSIS 2.04 distribution. That code comes without - * explicit copyright notices in tyhe source files or author names, it + * explicit copyright notices in the source files or author names, it * seems that it has been written by Justin Frankel; not sure about * the year, though. [wk 2005-11-28] * diff --git a/build-aux/speedo/w32/inst.nsi b/build-aux/speedo/w32/inst.nsi index 283166835..fa22cce46 100644 --- a/build-aux/speedo/w32/inst.nsi +++ b/build-aux/speedo/w32/inst.nsi @@ -45,11 +45,10 @@ Unicode true !define PACKAGE_SHORT "gnupg" !define PRETTY_PACKAGE "GNU Privacy Guard" !define PRETTY_PACKAGE_SHORT "GnuPG" -!define COMPANY "The GnuPG Project" -!define COPYRIGHT "Copyright (C) 2021 g10 Code GmbH" -!define DESCRIPTION "GnuPG: The GNU Privacy Guard for Windows" - !define INSTALL_DIR "GnuPG" +!define COMPANY "The GnuPG Project" +!define COPYRIGHT "Copyright (C) 2024 g10 Code GmbH" +!define DESCRIPTION "GnuPG: The GNU Privacy Guard for Windows" !define WELCOME_TITLE_ENGLISH \ "Welcome to the installation of GnuPG" @@ -63,13 +62,13 @@ Unicode true GnuPG includes an advanced key management facility and is compliant \ with the OpenPGP Internet standard as described in RFC-4880. \ \r\n\r\n$_CLICK \ - \r\n\r\n\r\n\r\n\r\nThis is GnuPG version ${VERSION}.\r\n\ + \r\n\r\n\r\n\r\n\r\nThis is GnuPG version ${VERSION} (64 bit).\r\n\ File version: ${PROD_VERSION}\r\n\ Release date: ${BUILD_ISODATE}" !define ABOUT_GERMAN \ "GnuPG is die häufigst verwendete Software zur Mail- und Datenverschlüsselung.\ \r\n\r\n$_CLICK \ - \r\n\r\n\r\n\r\n\r\nDies ist GnuPG Version ${VERSION}.\r\n\ + \r\n\r\n\r\n\r\n\r\nDies ist GnuPG Version ${VERSION} (64 bit).\r\n\ Dateiversion: ${PROD_VERSION}\r\n\ Releasedatum: ${BUILD_ISODATE}" @@ -91,6 +90,10 @@ SetCompressor lzma !include "LogicLib.nsh" !include "x64.nsh" +# Set the default installation directory. This is used by +# MultiUser.nsh which then sets the actual INSTDIR. +InstallDir "$PROGRAMFILES64\${INSTALL_DIR}" + # We support user mode installation but prefer system wide !define MULTIUSER_EXECUTIONLEVEL Highest !define MULTIUSER_MUI @@ -99,7 +102,8 @@ SetCompressor lzma !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "" !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "Software\${PACKAGE_SHORT}" !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "Install Directory" -!define MULTIUSER_INSTALLMODE_INSTDIR "${PACKAGE_SHORT}" +!define MULTIUSER_INSTALLMODE_INSTDIR "${INSTALL_DIR}" +!define MULTIUSER_USE_PROGRAMFILES64 !include "MultiUser.nsh" # Set the package name. Note that this name should not be suffixed @@ -115,12 +119,6 @@ OutFile "${NAME}-${VERSION}_${BUILD_DATESTR}.exe" #Icon "${TOP_SRCDIR}/doc/logo/gnupg-logo-icon.ico" #UninstallIcon "${TOP_SRCDIR}/doc/logo/gnupg-logo-icon.ico" -# Set the installation directory. -!ifndef INSTALL_DIR -!define INSTALL_DIR "GnuPG" -!endif -InstallDir "$PROGRAMFILES\${INSTALL_DIR}" - # Add version information to the file properties. VIProductVersion "${PROD_VERSION}" VIAddVersionKey "ProductName" "${PRETTY_PACKAGE_SHORT} (${VERSION})" @@ -250,7 +248,7 @@ LangString T_About ${LANG_GERMAN} "${ABOUT_GERMAN}" LangString T_GPLHeader ${LANG_ENGLISH} \ "This software is licensed under the terms of the GNU General Public \ License (GNU GPL)." -LangString T_GPLHeader ${LANG_GERMAN}} \ +LangString T_GPLHeader ${LANG_GERMAN} \ "Diese Software ist unter der GNU General Public License \ (GNU GPL) lizensiert." @@ -576,8 +574,32 @@ FunctionEnd # # Define the installer sections. # - +Var MYTMP Section "-gnupginst" + # Check if GnuPG is already installed and uninstall it (Update) + # + # Before GnuPG 2.5 the Windows installer was 32 bit + # so look for 32 bit installations, too. + SetRegView 32 +uninst_old_version: + ClearErrors + ReadRegStr $0 SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\GnuPG" "UninstallString" + IfErrors skip_uninst 0 + ReadRegStr $1 SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\GnuPG" "InstallLocation" + IfErrors skip_uninst 0 + + ExecWait '$0 /S _?=$1' + Delete /REBOOTOK "$1\gnupg-uninstall.exe" + RmDir "$1" +skip_uninst: + StrCmp $MYTMP "1" skip_uninst2 0 + SetRegView 64 + StrCpy $MYTMP "1" + goto uninst_old_version +skip_uninst2: + + # We are now 64 bit only + SetRegView 64 SetOutPath "$INSTDIR" File "${BUILD_DIR}/README.txt" @@ -590,23 +612,6 @@ Section "-gnupginst" WriteRegStr SHCTX "Software\GnuPG" "Install Directory" $INSTDIR - # If we are reinstalling, try to kill a possible running gpa using - # an already installed gpa. - ifFileExists "$INSTDIR\bin\launch-gpa.exe" 0 no_uiserver - nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"' - - no_uiserver: - - # If we are reinstalling, try to kill a possible running agent using - # an already installed gpgconf. - - ifFileExists "$INSTDIR\bin\gpgconf.exe" 0 no_gpgconf - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"' - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"' - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "keyboxd"' - - no_gpgconf: - # Add the bin directory to the PATH Push "$INSTDIR\bin" Call AddToPath @@ -669,7 +674,15 @@ Section "GnuPG" SEC_gnupg SetOutPath "$INSTDIR\share\gnupg" File "share/gnupg/distsigkey.gpg" - File "share/gnupg/sks-keyservers.netCA.pem" + File /nonfatal "share/gnupg/help.txt" + File /nonfatal "share/gnupg/help.de.txt" + File /nonfatal "share/gnupg/help.fr.txt" + File /nonfatal "share/gnupg/mail-tube.txt" + File /nonfatal "share/gnupg/mail-tube.de.txt" + File /nonfatal "share/gnupg/mail-tube.fr.txt" + File /nonfatal "share/gnupg/wks-utils.txt" + File /nonfatal "share/gnupg/wks-utils.de.txt" + File /nonfatal "share/gnupg/wks-utils.fr.txt" SetOutPath "$INSTDIR\share\doc\gnupg\examples" File "share/doc/gnupg/examples/pwpattern.list" @@ -820,7 +833,7 @@ SectionEnd Section "-assuan" SEC_assuan SetOutPath "$INSTDIR\bin" - File bin/libassuan-0.dll + File bin/libassuan-9.dll SetOutPath "$INSTDIR\lib" File /oname=libassuan.imp lib/libassuan.dll.a SetOutPath "$INSTDIR\include" @@ -836,23 +849,16 @@ Section "-ksba" SEC_ksba File include/ksba.h SectionEnd -Section "-gpgme" SEC_gpgme - SetOutPath "$INSTDIR\bin" - File bin/libgpgme-11.dll - File /nonfatal bin/libgpgme-glib-11.dll - File libexec/gpgme-w32spawn.exe - SetOutPath "$INSTDIR\lib" - File /oname=libgpgme.imp lib/libgpgme.dll.a - File /nonfatal /oname=libgpgme-glib.imp lib/libgpgme-glib.dll.a - SetOutPath "$INSTDIR\include" - File include/gpgme.h -SectionEnd - Section "-sqlite" SEC_sqlite SetOutPath "$INSTDIR\bin" File bin/libsqlite3-0.dll SectionEnd +Section "-ntbtls" SEC_ntbtls + SetOutPath "$INSTDIR\bin" + File bin/libntbtls-0.dll +SectionEnd + !ifdef WITH_GUI Section "-libiconv" SEC_libiconv SetOutPath "$INSTDIR\bin" @@ -1067,9 +1073,7 @@ Section "-un.gnupglast" nsExec::ExecToLog '"$INSTDIR\bin\launch-gpa" "--stop-server"' no_uiserver: ifFileExists "$INSTDIR\bin\gpgconf.exe" 0 no_gpgconf - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "gpg-agent"' - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "dirmngr"' - nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "keyboxd"' + nsExec::ExecToLog '"$INSTDIR\bin\gpgconf" "--kill" "all"' no_gpgconf: SectionEnd @@ -1217,15 +1221,6 @@ Section "-un.libiconv" Delete "$INSTDIR\bin\libiconv-2.dll" SectionEnd -Section "-un.gpgme" - Delete "$INSTDIR\bin\libgpgme-11.dll" - Delete "$INSTDIR\bin\libgpgme-glib-11.dll" - Delete "$INSTDIR\bin\gpgme-w32spawn.exe" - Delete "$INSTDIR\lib\libgpgme.imp" - Delete "$INSTDIR\lib\libgpgme-glib.imp" - Delete "$INSTDIR\include\gpgme.h" -SectionEnd - Section "-un.ksba" Delete "$INSTDIR\bin\libksba-8.dll" Delete "$INSTDIR\lib\libksba.imp" @@ -1233,7 +1228,7 @@ Section "-un.ksba" SectionEnd Section "-un.assuan" - Delete "$INSTDIR\bin\libassuan-0.dll" + Delete "$INSTDIR\bin\libassuan-9.dll" Delete "$INSTDIR\lib\libassuan.imp" Delete "$INSTDIR\include\assuan.h" SectionEnd @@ -1340,11 +1335,18 @@ Section "-un.gnupg" Delete "$INSTDIR\share\doc\gnupg\examples\pwpattern.list" RMDir "$INSTDIR\share\doc\gnupg\examples" + RMDir "$INSTDIR\share\doc\gnupg" + RMDir "$INSTDIR\share\doc" - Delete "$INSTDIR\share\gnupg\sks-keyservers.netCA.pem" Delete "$INSTDIR\share\gnupg\dirmngr-conf.skel" Delete "$INSTDIR\share\gnupg\distsigkey.gpg" Delete "$INSTDIR\share\gnupg\gpg-conf.skel" + Delete "$INSTDIR\share\gnupg\help.txt" + Delete "$INSTDIR\share\gnupg\help.*.txt" + Delete "$INSTDIR\share\gnupg\mail-tube.txt" + Delete "$INSTDIR\share\gnupg\mail-tube.*.txt" + Delete "$INSTDIR\share\gnupg\wks-utils.txt" + Delete "$INSTDIR\share\gnupg\wks-utils.*.txt" RMDir "$INSTDIR\share\gnupg" Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gnupg2.mo" @@ -1438,6 +1440,10 @@ Section "-un.sqlite" Delete "$INSTDIR\bin\libsqlite3-0.dll" SectionEnd +Section "-un.ntbtls" + Delete "$INSTDIR\bin\libntbtls-0.dll" +SectionEnd + Section "-un.gnupginst" # Delete standard stuff. Delete "$INSTDIR\README.txt" @@ -1467,17 +1473,11 @@ Function .onInit Call G4wRunOnce - SetOutPath $TEMP -#!ifdef SOURCES -# File /oname=gpgspltmp.bmp "${TOP_SRCDIR}/doc/logo/gnupg-logo-400px.bmp" -# # We play the tune only for the soruce installer -# File /oname=gpgspltmp.wav "${TOP_SRCDIR}/src/gnupg-splash.wav" -# g4wihelp::playsound $TEMP\gpgspltmp.wav -# g4wihelp::showsplash 2500 $TEMP\gpgspltmp.bmp - -# Delete $TEMP\gpgspltmp.bmp -# # Note that we delete gpgspltmp.wav in .onInst{Failed,Success} -#!endif + ${IfNot} ${RunningX64} + MessageBox MB_OK "Sorry this version runs only on x64 machines" + Abort + ${EndIf} + SetRegView 64 # We can't use TOP_SRCDIR dir as the name of the file needs to be # the same while building and running the installer. Thus we @@ -1488,7 +1488,7 @@ Function .onInit Var /GLOBAL changed_dir # Check if the install directory was modified on the command line - StrCmp "$INSTDIR" "$PROGRAMFILES\${INSTALL_DIR}" unmodified 0 + StrCmp "$INSTDIR" "$PROGRAMFILES64\${INSTALL_DIR}" unmodified 0 # It is modified. Save that value. StrCpy $changed_dir "$INSTDIR" @@ -1502,6 +1502,7 @@ initDone: FunctionEnd Function "un.onInit" + SetRegView 64 !insertmacro MULTIUSER_UNINIT FunctionEnd @@ -1610,7 +1611,6 @@ SectionEnd # # Now for the generic parts to end the installation. # -Var MYTMP # Last section is a hidden one. Section diff --git a/build-aux/speedo/w32/wixlib.wxs b/build-aux/speedo/w32/wixlib.wxs index 02568fe2f..3b595a668 100644 --- a/build-aux/speedo/w32/wixlib.wxs +++ b/build-aux/speedo/w32/wixlib.wxs @@ -61,9 +61,12 @@ and then manually edited: - + + + + @@ -82,9 +85,6 @@ and then manually edited: - - - @@ -94,8 +94,8 @@ and then manually edited: - - + + @@ -103,9 +103,6 @@ and then manually edited: - - - @@ -115,6 +112,9 @@ and then manually edited: + + + @@ -133,9 +133,6 @@ and then manually edited: - - - @@ -151,9 +148,6 @@ and then manually edited: - - - @@ -166,9 +160,6 @@ and then manually edited: - - - diff --git a/common/Makefile.am b/common/Makefile.am index d5ab038bf..a4840fbdd 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -58,14 +58,14 @@ common_sources = \ openpgpdefs.h \ gc-opt-flags.h \ sexp-parse.h \ - tlv.c tlv.h tlv-builder.c \ + tlv.c tlv.h tlv-builder.c tlv-parser.c \ init.c init.h \ sexputil.c \ sysutils.c sysutils.h \ homedir.c \ gettime.c gettime.h \ yesno.c \ - b64enc.c b64dec.c zb32.c zb32.h \ + zb32.c zb32.h \ convert.c \ percent.c \ mbox-util.c mbox-util.h \ @@ -97,8 +97,8 @@ common_sources = \ openpgp-fpr.c \ comopt.c comopt.h \ compliance.c compliance.h \ - pkscreening.c pkscreening.h - + pkscreening.c pkscreening.h \ + kem.c if HAVE_W32_SYSTEM common_sources += w32-reg.c w32-cmdline.c @@ -161,15 +161,16 @@ module_tests = t-stringhelp t-timestuff \ t-convert t-percent t-gettime t-sysutils t-sexputil \ t-session-env t-openpgp-oid t-ssh-utils \ t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \ - t-name-value t-ccparray t-recsel t-w32-cmdline + t-name-value t-ccparray t-recsel t-w32-cmdline t-exechelp + if HAVE_W32_SYSTEM module_tests += t-w32-reg else -module_tests += t-exechelp t-exectool +module_tests += t-exectool endif if MAINTAINER_MODE -module_maint_tests = t-helpfile t-b64 +module_maint_tests = t-helpfile else module_maint_tests = endif @@ -196,7 +197,6 @@ t_gettime_LDADD = $(t_common_ldadd) t_sysutils_LDADD = $(t_common_ldadd) t_helpfile_LDADD = $(t_common_ldadd) t_sexputil_LDADD = $(t_common_ldadd) -t_b64_LDADD = $(t_common_ldadd) t_exechelp_LDADD = $(t_common_ldadd) t_exectool_LDADD = $(t_common_ldadd) t_session_env_LDADD = $(t_common_ldadd) diff --git a/common/asshelp.c b/common/asshelp.c index eb3e41bf5..0152d1243 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -39,7 +39,6 @@ #include "i18n.h" #include "util.h" -#include "exechelp.h" #include "sysutils.h" #include "status.h" #include "membuf.h" @@ -54,9 +53,9 @@ /* The time we wait until the agent or the dirmngr are ready for operation after we started them before giving up. */ -#define SECS_TO_WAIT_FOR_AGENT 5 -#define SECS_TO_WAIT_FOR_KEYBOXD 5 -#define SECS_TO_WAIT_FOR_DIRMNGR 5 +#define SECS_TO_WAIT_FOR_AGENT 8 +#define SECS_TO_WAIT_FOR_KEYBOXD 8 +#define SECS_TO_WAIT_FOR_DIRMNGR 8 /* A bitfield that specifies the assuan categories to log. This is identical to the default log handler of libassuan. We need to do @@ -129,6 +128,21 @@ set_libassuan_log_cats (unsigned int newcats) } +/* Get the last Windows error from an Assuan socket function and print + * the raw error code using log_info. */ +void +log_libassuan_system_error (assuan_fd_t fd) +{ + int w32err = 0; + + if (assuan_sock_get_flag (fd, "w32_error", &w32err)) + w32err = -1; /* Old Libassuan or not Windows. */ + + if (w32err != -1) + log_info ("system error code: %d (0x%x)\n", w32err, w32err); +} + + static gpg_error_t send_one_option (assuan_context_t ctx, gpg_err_source_t errsource, @@ -386,7 +400,8 @@ start_new_service (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { @@ -445,7 +460,7 @@ start_new_service (assuan_context_t *r_ctx, } err = assuan_socket_connect (ctx, sockname, 0, connect_flags); - if (err && autostart) + if (err && (flags & ASSHELP_FLAG_AUTOSTART)) { char *abs_homedir; lock_spawn_t lock; @@ -512,8 +527,6 @@ start_new_service (assuan_context_t *r_ctx, 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"; @@ -523,16 +536,11 @@ start_new_service (assuan_context_t *r_ctx, && assuan_socket_connect (ctx, sockname, 0, connect_flags)) { #ifdef HAVE_W32_SYSTEM - err = gnupg_spawn_process_detached (program? program : program_name, - argv, NULL); + err = gpgrt_process_spawn (program? program : program_name, argv, + GPGRT_PROCESS_DETACHED, NULL, 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); + err = gpgrt_process_spawn (program? program : program_name, argv, + 0, NULL, NULL); #endif /*!W32*/ if (err) log_error ("failed to start %s '%s': %s\n", @@ -551,7 +559,8 @@ start_new_service (assuan_context_t *r_ctx, xfree (sockname); if (err) { - if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED) + if ((flags & ASSHELP_FLAG_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); @@ -603,55 +612,58 @@ start_new_gpg_agent (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + 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, + flags, 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. + supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set. 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, + unsigned int flags, + 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, + flags, 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. + supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set. 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, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { #ifndef USE_DIRMNGR_AUTO_START - autostart = 0; + flags &= ~ASSHELP_FLAG_AUTOSTART; /* Clear flag. */ #endif return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR, errsource, dirmngr_program, NULL, NULL, NULL, - autostart, verbose, debug, + flags, verbose, debug, status_cb, status_cb_arg); } @@ -695,7 +707,7 @@ get_assuan_server_version (assuan_context_t ctx, int mode, char **r_version) /* Print a warning if the server's version number is less than our * version number. Returns an error code on a connection problem. - * CTX is the Assuan context, SERVERNAME is the name of teh server, + * CTX is the Assuan context, SERVERNAME is the name of the server, * STATUS_FUNC and STATUS_FUNC_DATA is a callback to emit status * messages. If PRINT_HINTS is set additional hints are printed. For * MODE see get_assuan_server_version. */ diff --git a/common/asshelp.h b/common/asshelp.h index e7e43bd1b..cde6e226f 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -37,6 +37,8 @@ #include "util.h" /*-- asshelp.c --*/ +#define ASSHELP_FLAG_AUTOSTART 1 /* Autostart the new service. */ + void setup_libassuan_logging (unsigned int *debug_var_address, int (*log_monitor)(assuan_context_t ctx, @@ -44,6 +46,8 @@ void setup_libassuan_logging (unsigned int *debug_var_address, const char *msg)); void set_libassuan_log_cats (unsigned int newcats); +void log_libassuan_system_error (assuan_fd_t fd); + gpg_error_t send_pinentry_environment (assuan_context_t ctx, @@ -61,7 +65,8 @@ start_new_gpg_agent (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); @@ -71,7 +76,8 @@ 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, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); @@ -81,7 +87,8 @@ 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, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); diff --git a/common/audit.c b/common/audit.c index ae0d45216..551563c61 100644 --- a/common/audit.c +++ b/common/audit.c @@ -44,9 +44,9 @@ struct log_item_s 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; + ksba_cert_t cert; /* A certificate or NULL. */ + unsigned int have_err:1; + unsigned int have_intvalue:1; }; typedef struct log_item_s *log_item_t; diff --git a/common/audit.h b/common/audit.h index 05f39533d..9fadba1b2 100644 --- a/common/audit.h +++ b/common/audit.h @@ -76,7 +76,7 @@ typedef enum /* The signature is a detached one. */ AUDIT_CERT_ONLY_SIG, - /* A certifciate only signature has been detected. */ + /* A certificate only signature has been detected. */ AUDIT_DATA_HASH_ALGO, /* int */ /* The hash algo given as argument is used for the data. This diff --git a/common/b64dec.c b/common/b64dec.c deleted file mode 100644 index 6af494b79..000000000 --- a/common/b64dec.c +++ /dev/null @@ -1,254 +0,0 @@ -/* b64dec.c - Simple Base64 decoder. - * Copyright (C) 2008, 2011 Free Software Foundation, Inc. - * Copyright (C) 2008, 2011, 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 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 . - */ - -#include -#include -#include -#include -#include -#include - -#include "i18n.h" -#include "util.h" - - -/* The reverse base-64 list used for base-64 decoding. */ -static unsigned char const asctobin[128] = - { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, - 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff - }; - -enum decoder_states - { - s_init, s_idle, s_lfseen, s_beginseen, s_waitheader, s_waitblank, s_begin, - s_b64_0, s_b64_1, s_b64_2, s_b64_3, - s_waitendtitle, s_waitend - }; - - - -/* Initialize the context for the base64 decoder. If TITLE is NULL a - plain base64 decoding is done. If it is the empty string the - decoder will skip everything until a "-----BEGIN " line has been - seen, decoding ends at a "----END " line. */ -gpg_error_t -b64dec_start (struct b64state *state, const char *title) -{ - memset (state, 0, sizeof *state); - if (title) - { - state->title = xtrystrdup (title); - if (!state->title) - state->lasterr = gpg_error_from_syserror (); - else - state->idx = s_init; - } - else - state->idx = s_b64_0; - return state->lasterr; -} - - -/* Do in-place decoding of base-64 data of LENGTH in BUFFER. Stores the - new length of the buffer at R_NBYTES. */ -gpg_error_t -b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes) -{ - enum decoder_states ds = state->idx; - unsigned char val = state->radbuf[0]; - int pos = state->quad_count; - char *d, *s; - - if (state->lasterr) - return state->lasterr; - - if (state->stop_seen) - { - *r_nbytes = 0; - state->lasterr = gpg_error (GPG_ERR_EOF); - xfree (state->title); - state->title = NULL; - return state->lasterr; - } - - for (s=d=buffer; length && !state->stop_seen; length--, s++) - { - again: - switch (ds) - { - case s_idle: - if (*s == '\n') - { - ds = s_lfseen; - pos = 0; - } - break; - case s_init: - ds = s_lfseen; - /* fall through */ - case s_lfseen: - if (*s != "-----BEGIN "[pos]) - { - ds = s_idle; - goto again; - } - else if (pos == 10) - { - pos = 0; - ds = s_beginseen; - } - else - pos++; - break; - case s_beginseen: - if (*s != "PGP "[pos]) - ds = s_begin; /* Not a PGP armor. */ - else if (pos == 3) - ds = s_waitheader; - else - pos++; - break; - case s_waitheader: - if (*s == '\n') - ds = s_waitblank; - break; - case s_waitblank: - if (*s == '\n') - ds = s_b64_0; /* blank line found. */ - else if (*s == ' ' || *s == '\r' || *s == '\t') - ; /* Ignore spaces. */ - else - { - /* Armor header line. Note that we don't care that our - * FSM accepts a header prefixed with spaces. */ - ds = s_waitheader; /* Wait for next header. */ - } - break; - case s_begin: - if (*s == '\n') - ds = s_b64_0; - break; - case s_b64_0: - case s_b64_1: - case s_b64_2: - case s_b64_3: - { - int c; - - if (*s == '-' && state->title) - { - /* Not a valid Base64 character: assume end - header. */ - ds = s_waitend; - } - else if (*s == '=') - { - /* Pad character: stop */ - if (ds == s_b64_1) - *d++ = val; - ds = state->title? s_waitendtitle : s_waitend; - } - else if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t') - ; /* Skip white spaces. */ - else if ( (*s & 0x80) - || (c = asctobin[*(unsigned char *)s]) == 255) - { - /* Skip invalid encodings. */ - state->invalid_encoding = 1; - } - else if (ds == s_b64_0) - { - val = c << 2; - ds = s_b64_1; - } - else if (ds == s_b64_1) - { - val |= (c>>4)&3; - *d++ = val; - val = (c<<4)&0xf0; - ds = s_b64_2; - } - else if (ds == s_b64_2) - { - val |= (c>>2)&15; - *d++ = val; - val = (c<<6)&0xc0; - ds = s_b64_3; - } - else - { - val |= c&0x3f; - *d++ = val; - ds = s_b64_0; - } - } - break; - case s_waitendtitle: - if (*s == '-') - ds = s_waitend; - break; - case s_waitend: - if ( *s == '\n') - state->stop_seen = 1; - break; - default: - BUG(); - } - } - - - state->idx = ds; - state->radbuf[0] = val; - state->quad_count = pos; - *r_nbytes = (d -(char*) buffer); - return 0; -} - - -/* This function needs to be called before releasing the decoder - state. It may return an error code in case an encoding error has - been found during decoding. */ -gpg_error_t -b64dec_finish (struct b64state *state) -{ - xfree (state->title); - state->title = NULL; - - if (state->lasterr) - return state->lasterr; - - return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0; -} diff --git a/common/b64enc.c b/common/b64enc.c deleted file mode 100644 index d633048ea..000000000 --- a/common/b64enc.c +++ /dev/null @@ -1,422 +0,0 @@ -/* b64enc.c - Simple Base64 encoder. - * Copyright (C) 2001, 2003, 2004, 2008, 2010, - * 2011 Free Software Foundation, Inc. - * Copyright (C) 2001, 2003, 2004, 2008, 2010, - * 2011 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 . - */ - -#include -#include -#include -#include -#include -#include - -#include "i18n.h" -#include "util.h" - -#define B64ENC_DID_HEADER 1 -#define B64ENC_DID_TRAILER 2 -#define B64ENC_NO_LINEFEEDS 16 -#define B64ENC_USE_PGPCRC 32 - -/* The base-64 character list */ -static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -/* Stuff required to create the OpenPGP CRC. This crc_table has been - created using this code: - - #include - #include - - #define CRCPOLY 0x864CFB - - int - main (void) - { - int i, j; - uint32_t t; - uint32_t crc_table[256]; - - crc_table[0] = 0; - for (i=j=0; j < 128; j++ ) - { - t = crc_table[j]; - if ( (t & 0x00800000) ) - { - t <<= 1; - crc_table[i++] = t ^ CRCPOLY; - crc_table[i++] = t; - } - else - { - t <<= 1; - crc_table[i++] = t; - crc_table[i++] = t ^ CRCPOLY; - } - } - - puts ("static const u32 crc_table[256] = {"); - for (i=j=0; i < 256; i++) - { - printf ("%s 0x%08lx", j? "":" ", (unsigned long)crc_table[i]); - if (i != 255) - { - putchar (','); - if ( ++j > 5) - { - j = 0; - putchar ('\n'); - } - } - } - puts ("\n};"); - return 0; - } -*/ -#define CRCINIT 0xB704CE -static const u32 crc_table[256] = { - 0x00000000, 0x00864cfb, 0x018ad50d, 0x010c99f6, 0x0393e6e1, 0x0315aa1a, - 0x021933ec, 0x029f7f17, 0x07a18139, 0x0727cdc2, 0x062b5434, 0x06ad18cf, - 0x043267d8, 0x04b42b23, 0x05b8b2d5, 0x053efe2e, 0x0fc54e89, 0x0f430272, - 0x0e4f9b84, 0x0ec9d77f, 0x0c56a868, 0x0cd0e493, 0x0ddc7d65, 0x0d5a319e, - 0x0864cfb0, 0x08e2834b, 0x09ee1abd, 0x09685646, 0x0bf72951, 0x0b7165aa, - 0x0a7dfc5c, 0x0afbb0a7, 0x1f0cd1e9, 0x1f8a9d12, 0x1e8604e4, 0x1e00481f, - 0x1c9f3708, 0x1c197bf3, 0x1d15e205, 0x1d93aefe, 0x18ad50d0, 0x182b1c2b, - 0x192785dd, 0x19a1c926, 0x1b3eb631, 0x1bb8faca, 0x1ab4633c, 0x1a322fc7, - 0x10c99f60, 0x104fd39b, 0x11434a6d, 0x11c50696, 0x135a7981, 0x13dc357a, - 0x12d0ac8c, 0x1256e077, 0x17681e59, 0x17ee52a2, 0x16e2cb54, 0x166487af, - 0x14fbf8b8, 0x147db443, 0x15712db5, 0x15f7614e, 0x3e19a3d2, 0x3e9fef29, - 0x3f9376df, 0x3f153a24, 0x3d8a4533, 0x3d0c09c8, 0x3c00903e, 0x3c86dcc5, - 0x39b822eb, 0x393e6e10, 0x3832f7e6, 0x38b4bb1d, 0x3a2bc40a, 0x3aad88f1, - 0x3ba11107, 0x3b275dfc, 0x31dced5b, 0x315aa1a0, 0x30563856, 0x30d074ad, - 0x324f0bba, 0x32c94741, 0x33c5deb7, 0x3343924c, 0x367d6c62, 0x36fb2099, - 0x37f7b96f, 0x3771f594, 0x35ee8a83, 0x3568c678, 0x34645f8e, 0x34e21375, - 0x2115723b, 0x21933ec0, 0x209fa736, 0x2019ebcd, 0x228694da, 0x2200d821, - 0x230c41d7, 0x238a0d2c, 0x26b4f302, 0x2632bff9, 0x273e260f, 0x27b86af4, - 0x252715e3, 0x25a15918, 0x24adc0ee, 0x242b8c15, 0x2ed03cb2, 0x2e567049, - 0x2f5ae9bf, 0x2fdca544, 0x2d43da53, 0x2dc596a8, 0x2cc90f5e, 0x2c4f43a5, - 0x2971bd8b, 0x29f7f170, 0x28fb6886, 0x287d247d, 0x2ae25b6a, 0x2a641791, - 0x2b688e67, 0x2beec29c, 0x7c3347a4, 0x7cb50b5f, 0x7db992a9, 0x7d3fde52, - 0x7fa0a145, 0x7f26edbe, 0x7e2a7448, 0x7eac38b3, 0x7b92c69d, 0x7b148a66, - 0x7a181390, 0x7a9e5f6b, 0x7801207c, 0x78876c87, 0x798bf571, 0x790db98a, - 0x73f6092d, 0x737045d6, 0x727cdc20, 0x72fa90db, 0x7065efcc, 0x70e3a337, - 0x71ef3ac1, 0x7169763a, 0x74578814, 0x74d1c4ef, 0x75dd5d19, 0x755b11e2, - 0x77c46ef5, 0x7742220e, 0x764ebbf8, 0x76c8f703, 0x633f964d, 0x63b9dab6, - 0x62b54340, 0x62330fbb, 0x60ac70ac, 0x602a3c57, 0x6126a5a1, 0x61a0e95a, - 0x649e1774, 0x64185b8f, 0x6514c279, 0x65928e82, 0x670df195, 0x678bbd6e, - 0x66872498, 0x66016863, 0x6cfad8c4, 0x6c7c943f, 0x6d700dc9, 0x6df64132, - 0x6f693e25, 0x6fef72de, 0x6ee3eb28, 0x6e65a7d3, 0x6b5b59fd, 0x6bdd1506, - 0x6ad18cf0, 0x6a57c00b, 0x68c8bf1c, 0x684ef3e7, 0x69426a11, 0x69c426ea, - 0x422ae476, 0x42aca88d, 0x43a0317b, 0x43267d80, 0x41b90297, 0x413f4e6c, - 0x4033d79a, 0x40b59b61, 0x458b654f, 0x450d29b4, 0x4401b042, 0x4487fcb9, - 0x461883ae, 0x469ecf55, 0x479256a3, 0x47141a58, 0x4defaaff, 0x4d69e604, - 0x4c657ff2, 0x4ce33309, 0x4e7c4c1e, 0x4efa00e5, 0x4ff69913, 0x4f70d5e8, - 0x4a4e2bc6, 0x4ac8673d, 0x4bc4fecb, 0x4b42b230, 0x49ddcd27, 0x495b81dc, - 0x4857182a, 0x48d154d1, 0x5d26359f, 0x5da07964, 0x5cace092, 0x5c2aac69, - 0x5eb5d37e, 0x5e339f85, 0x5f3f0673, 0x5fb94a88, 0x5a87b4a6, 0x5a01f85d, - 0x5b0d61ab, 0x5b8b2d50, 0x59145247, 0x59921ebc, 0x589e874a, 0x5818cbb1, - 0x52e37b16, 0x526537ed, 0x5369ae1b, 0x53efe2e0, 0x51709df7, 0x51f6d10c, - 0x50fa48fa, 0x507c0401, 0x5542fa2f, 0x55c4b6d4, 0x54c82f22, 0x544e63d9, - 0x56d11cce, 0x56575035, 0x575bc9c3, 0x57dd8538 -}; - - -static gpg_error_t -enc_start (struct b64state *state, FILE *fp, estream_t stream, - const char *title) -{ - memset (state, 0, sizeof *state); - state->fp = fp; - state->stream = stream; - state->lasterr = 0; - if (title && !*title) - state->flags |= B64ENC_NO_LINEFEEDS; - else if (title) - { - if (!strncmp (title, "PGP ", 4)) - { - state->flags |= B64ENC_USE_PGPCRC; - state->crc = CRCINIT; - } - state->title = xtrystrdup (title); - if (!state->title) - state->lasterr = gpg_error_from_syserror (); - } - return state->lasterr; -} - - -/* Prepare for base-64 writing to the stream FP. If TITLE is not NULL - and not an empty string, this string will be used as the title for - the armor lines, with TITLE being an empty string, we don't write - the header lines and furthermore even don't write any linefeeds. - If TITLE starts with "PGP " the OpenPGP CRC checksum will be - written as well. With TITLE being NULL, we merely don't write - header but make sure that lines are not too long. Note, that we - don't write any output unless at least one byte get written using - b64enc_write. */ -gpg_error_t -b64enc_start (struct b64state *state, FILE *fp, const char *title) -{ - return enc_start (state, fp, NULL, title); -} - -/* Same as b64enc_start but takes an estream. */ -gpg_error_t -b64enc_start_es (struct b64state *state, estream_t fp, const char *title) -{ - return enc_start (state, NULL, fp, title); -} - - -static int -my_fputs (const char *string, struct b64state *state) -{ - if (state->stream) - return es_fputs (string, state->stream); - else - return fputs (string, state->fp); -} - - -/* Write NBYTES from BUFFER to the Base 64 stream identified by - STATE. With BUFFER and NBYTES being 0, merely do a fflush on the - stream. */ -gpg_error_t -b64enc_write (struct b64state *state, const void *buffer, size_t nbytes) -{ - unsigned char radbuf[4]; - int idx, quad_count; - const unsigned char *p; - - if (state->lasterr) - return state->lasterr; - - if (!nbytes) - { - if (buffer) - if (state->stream? es_fflush (state->stream) : fflush (state->fp)) - goto write_error; - return 0; - } - - if (!(state->flags & B64ENC_DID_HEADER)) - { - if (state->title) - { - if ( my_fputs ("-----BEGIN ", state) == EOF - || my_fputs (state->title, state) == EOF - || my_fputs ("-----\n", state) == EOF) - goto write_error; - if ( (state->flags & B64ENC_USE_PGPCRC) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - - state->flags |= B64ENC_DID_HEADER; - } - - idx = state->idx; - quad_count = state->quad_count; - assert (idx < 4); - memcpy (radbuf, state->radbuf, idx); - - if ( (state->flags & B64ENC_USE_PGPCRC) ) - { - size_t n; - u32 crc = state->crc; - - for (p=buffer, n=nbytes; n; p++, n-- ) - crc = ((u32)crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ *p]; - state->crc = (crc & 0x00ffffff); - } - - for (p=buffer; nbytes; p++, nbytes--) - { - radbuf[idx++] = *p; - if (idx > 2) - { - char tmp[4]; - - tmp[0] = bintoasc[(*radbuf >> 2) & 077]; - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; - tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - tmp[3] = bintoasc[radbuf[2]&077]; - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - idx = 0; - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - idx = 0; - if (ferror (state->fp)) - goto write_error; - } - if (++quad_count >= (64/4)) - { - quad_count = 0; - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - } - } - memcpy (state->radbuf, radbuf, idx); - state->idx = idx; - state->quad_count = quad_count; - return 0; - - write_error: - state->lasterr = gpg_error_from_syserror (); - if (state->title) - { - xfree (state->title); - state->title = NULL; - } - return state->lasterr; -} - - -gpg_error_t -b64enc_finish (struct b64state *state) -{ - gpg_error_t err = 0; - unsigned char radbuf[4]; - int idx, quad_count; - char tmp[4]; - - if (state->lasterr) - return state->lasterr; - - if (!(state->flags & B64ENC_DID_HEADER)) - goto cleanup; - - /* Flush the base64 encoding */ - idx = state->idx; - quad_count = state->quad_count; - assert (idx < 4); - memcpy (radbuf, state->radbuf, idx); - - if (idx) - { - tmp[0] = bintoasc[(*radbuf>>2)&077]; - if (idx == 1) - { - tmp[1] = bintoasc[((*radbuf << 4) & 060) & 077]; - tmp[2] = '='; - tmp[3] = '='; - } - else - { - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; - tmp[2] = bintoasc[((radbuf[1] << 2) & 074) & 077]; - tmp[3] = '='; - } - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - if (ferror (state->fp)) - goto write_error; - } - - if (++quad_count >= (64/4)) - { - quad_count = 0; - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - } - - /* Finish the last line and write the trailer. */ - if (quad_count - && !(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - - if ( (state->flags & B64ENC_USE_PGPCRC) ) - { - /* Write the CRC. */ - my_fputs ("=", state); - radbuf[0] = state->crc >>16; - radbuf[1] = state->crc >> 8; - radbuf[2] = state->crc; - tmp[0] = bintoasc[(*radbuf>>2)&077]; - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; - tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - tmp[3] = bintoasc[radbuf[2]&077]; - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - if (ferror (state->fp)) - goto write_error; - } - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - - if (state->title) - { - if ( my_fputs ("-----END ", state) == EOF - || my_fputs (state->title, state) == EOF - || my_fputs ("-----\n", state) == EOF) - goto write_error; - } - - goto cleanup; - - write_error: - err = gpg_error_from_syserror (); - - cleanup: - if (state->title) - { - xfree (state->title); - state->title = NULL; - } - state->fp = NULL; - state->stream = NULL; - state->lasterr = err; - return err; -} diff --git a/common/call-gpg.c b/common/call-gpg.c index a4723ca43..75943a315 100644 --- a/common/call-gpg.c +++ b/common/call-gpg.c @@ -29,6 +29,7 @@ #include #include "call-gpg.h" +#include "sysutils.h" #include "exechelp.h" #include "i18n.h" #include "logging.h" @@ -428,9 +429,9 @@ _gpg_encrypt (ctrl_t ctrl, assert ((reader_mb == NULL) != (cipher_stream == NULL)); /* Create two pipes. */ - err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); + err = gnupg_create_pipe (outbound_fds, GNUPG_PIPE_OUTBOUND); if (!err) - err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); + err = gnupg_create_pipe (inbound_fds, GNUPG_PIPE_INBOUND); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); @@ -612,9 +613,9 @@ _gpg_decrypt (ctrl_t ctrl, assert ((reader_mb == NULL) != (plain_stream == NULL)); /* Create two pipes. */ - err = gnupg_create_outbound_pipe (outbound_fds, NULL, 0); + err = gnupg_create_pipe (outbound_fds, GNUPG_PIPE_OUTBOUND); if (!err) - err = gnupg_create_inbound_pipe (inbound_fds, NULL, 0); + err = gnupg_create_pipe (inbound_fds, GNUPG_PIPE_INBOUND); if (err) { log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); diff --git a/common/compliance.c b/common/compliance.c index 59d94038d..6c271c199 100644 --- a/common/compliance.c +++ b/common/compliance.c @@ -40,11 +40,44 @@ static int initialized; static int module; + /* This value is used by DSA and RSA checks in addition to the hard - * coded length checks. It allows to increase the required key length - * using a confue file. */ + * coded length checks. It allows one to increase the required key length + * using a config file. */ static unsigned int min_compliant_rsa_length; + +/* Kludge to allow testing of the compliance options while not yet + * approved. */ +static int +get_assumed_de_vs_compliance (void) +{ +#if 0 /* Set to 1 if the software suite has been approved. */ + return 0; +#else + static int value = -1; + + if (value == -1) + { + const char *s = getenv ("GNUPG_ASSUME_COMPLIANCE"); + value = (s && !strcmp (s, "de-vs")); +#ifdef HAVE_W32_SYSTEM + if (!value) + { + char *tmp; + tmp = read_w32_registry_string (NULL, + gnupg_registry_dir (), + "GNUPG_ASSUME_COMPLIANCE"); + if (tmp && !strcmp (tmp, "de-vs")) + value = 1; + xfree (tmp); + } +#endif /* W32 */ + } + return value > 0; +#endif +} + /* Return the address of a compliance cache variable for COMPLIANCE. * If no such variable exists NULL is returned. FOR_RNG returns the * cache variable for the RNG compliance check. */ @@ -70,6 +103,13 @@ get_compliance_cache (enum gnupg_compliance_mode compliance, int for_rng) case CO_DE_VS: ptr = for_rng? &r_de_vs : &s_de_vs ; break; } + if (ptr && compliance == CO_DE_VS) + { + if (get_assumed_de_vs_compliance ()) + *ptr = 1; + } + + return ptr; } @@ -139,7 +179,7 @@ gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo, gcry_mpi_t key[], unsigned int keylength, const char *curvename) { - enum { is_rsa, is_dsa, is_elg, is_ecc } algotype; + enum { is_rsa, is_dsa, is_elg, is_ecc, is_kem } algotype; int result = 0; if (! initialized) @@ -173,6 +213,10 @@ gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo, case PUBKEY_ALGO_ELGAMAL: return 0; /* Signing with Elgamal is not at all supported. */ + case PUBKEY_ALGO_KYBER: + algotype = is_kem; + break; + default: /* Unknown. */ return 0; } @@ -227,6 +271,23 @@ gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo, || !strcmp (curvename, "brainpoolP512r1"))); break; + case is_kem: + if (!curvename && key) + { + curve = openpgp_oid_to_str (key[0]); + curvename = openpgp_oid_to_curve (curve, 0); + if (!curvename) + curvename = curve; + } + + result = (curvename + && (keylength == 768 || keylength == 1024) + && (algo == PUBKEY_ALGO_KYBER) + && (!strcmp (curvename, "brainpoolP256r1") + || !strcmp (curvename, "brainpoolP384r1") + || !strcmp (curvename, "brainpoolP512r1"))); + break; + default: result = 0; } @@ -256,6 +317,13 @@ gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance, if (! initialized) return 1; + /* Map the the generic ECC algo to ECDSA if requested. */ + if ((algo_flags & PK_ALGO_FLAG_ECC18) + && algo == GCRY_PK_ECC + && (use == PK_USE_VERIFICATION + || use == PK_USE_SIGNING)) + algo = GCRY_PK_ECDSA; + switch (compliance) { case CO_DE_VS: @@ -280,7 +348,6 @@ gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance, default: log_assert (!"reached"); } - (void)algo_flags; break; case PUBKEY_ALGO_DSA: @@ -301,7 +368,7 @@ gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance, result = (use == PK_USE_DECRYPTION); break; - case PUBKEY_ALGO_ECDH: + case PUBKEY_ALGO_ECDH: /* Same value as GCRY_PK_ECC, i.e. 18 */ case GCRY_PK_ECDH: if (use == PK_USE_DECRYPTION) result = 1; @@ -359,6 +426,31 @@ gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance, result = 0; break; + case PUBKEY_ALGO_KYBER: + if (use == PK_USE_DECRYPTION) + result = 1; + else if (use == PK_USE_ENCRYPTION) + { + char *curve = NULL; + + if (!curvename && key) + { + curve = openpgp_oid_to_str (key[0]); + curvename = openpgp_oid_to_curve (curve, 0); + if (!curvename) + curvename = curve; + } + + result = (curvename + && (keylength == 768 || keylength == 1024) + && (!strcmp (curvename, "brainpoolP256r1") + || !strcmp (curvename, "brainpoolP384r1") + || !strcmp (curvename, "brainpoolP512r1"))); + + xfree (curve); + } + break; + default: break; } @@ -549,6 +641,9 @@ gnupg_rng_is_compliant (enum gnupg_compliance_mode compliance) int *result; int res; + /* #warning debug code ahead */ + /* return 1; */ + result = get_compliance_cache (compliance, 1); if (result && *result != -1) @@ -650,7 +745,7 @@ gnupg_status_compliance_flag (enum gnupg_compliance_mode compliance) case CO_PGP8: log_assert (!"no status code assigned for this compliance mode"); case CO_DE_VS: - return "23"; + return get_assumed_de_vs_compliance ()? "2023" : "23"; } log_assert (!"invalid compliance mode"); } diff --git a/common/compliance.h b/common/compliance.h index ead11472c..111fdc74b 100644 --- a/common/compliance.h +++ b/common/compliance.h @@ -50,6 +50,7 @@ enum pk_use_case /* Flags to distinguish public key algorithm variants. */ #define PK_ALGO_FLAG_RSAPSS 1 /* Use rsaPSS padding. */ +#define PK_ALGO_FLAG_ECC18 256 /* GCRY_PK_ECC is used in a generic way. */ int gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo, diff --git a/common/dotlock.c b/common/dotlock.c index ab0a5a6a3..19611ab7a 100644 --- a/common/dotlock.c +++ b/common/dotlock.c @@ -291,6 +291,7 @@ # include # include # include +# include #endif #include #include @@ -393,9 +394,18 @@ struct dotlock_handle 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. */ + unsigned int by_parent:1; /* Parent does the locking. */ + unsigned int no_write:1; /* No write to the lockfile. */ int extra_fd; /* A place for the caller to store an FD. */ + /* An optional info callback - see dotlock_set_info_cb. */ + int (*info_cb)(dotlock_t, void *, + enum dotlock_reasons reason, + const char *,...); + void *info_cb_value; + + #ifdef HAVE_DOSISH_SYSTEM HANDLE lockhd; /* The W32 handle of the lock file. */ #else /*!HAVE_DOSISH_SYSTEM */ @@ -545,8 +555,15 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) 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 (errno != ENOENT) + { + my_info_2 ("error opening lockfile '%s': %s\n", + h->lockname, strerror(errno) ); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "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. */ @@ -564,6 +581,10 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) { int e = errno; my_info_1 ("error reading lockfile '%s'\n", h->lockname ); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "error reading lockfile '%s': %s\n", + h->lockname, strerror (errno) ); close (fd); if (buffer != buffer_space) xfree (buffer); @@ -583,6 +604,9 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) if (nread < 11) { my_info_1 ("invalid size of lockfile '%s'\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE, + "invalid size of lockfile '%s'\n", h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); @@ -594,6 +618,9 @@ read_lockfile (dotlock_t h, int *same_node, int *r_fd) || !pid ) { my_error_2 ("invalid pid %d in lockfile '%s'\n", pid, h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE, + "invalid pid %d in lockfile '%s'\n", pid, h->lockname); if (buffer != buffer_space) xfree (buffer); my_set_errno (EINVAL); @@ -655,6 +682,82 @@ use_hardlinks_p (const char *tname) #ifdef HAVE_POSIX_SYSTEM +static int +dotlock_get_process_id (dotlock_t h) +{ + return h->by_parent? (int)getppid(): (int)getpid(); +} + +static int +dotlock_detect_tname (dotlock_t h) +{ + struct stat sb; + DIR *dir; + char *dirname; + char *basename; + struct dirent *d; + int r; + + if (stat (h->lockname, &sb)) + return -1; + + basename = make_basename (h->lockname, NULL); + dirname = make_dirname (h->lockname); + + dir = opendir (dirname); + if (dir == NULL) + { + xfree (basename); + xfree (dirname); + return -1; + } + + while ((d = readdir (dir))) + if (sb.st_ino == d->d_ino && strcmp (d->d_name, basename)) + break; + + if (d) + { + int len = strlen (h->tname); + int dlen = strlen (d->d_name); + const char *tname_path; + + if (dlen > len) + { + closedir (dir); + xfree (basename); + xfree (dirname); + return -1; + } + + strcpy (stpcpy (stpcpy (h->tname, dirname), DIRSEP_S), d->d_name); + h->use_o_excl = 0; + tname_path = strchr (h->tname + strlen (dirname) + 2, '.'); + if (!tname_path) + { + closedir (dir); + xfree (basename); + xfree (dirname); + return -1; + } + h->nodename_off = tname_path - h->tname + 1; + } + else + h->use_o_excl = 1; + + r = closedir (dir); + if (r) + { + xfree (basename); + xfree (dirname); + return r; + } + + xfree (basename); + xfree (dirname); + return 0; +} + /* Locking core for Unix. It used a temporary file and the link system call to make locking an atomic operation. */ static dotlock_t @@ -667,8 +770,10 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) int dirpartlen; struct utsname utsbuf; size_t tnamelen; + int pid; - snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() ); + pid = dotlock_get_process_id (h); + snprintf (pidstr, sizeof pidstr, "%10d\n", pid); /* Create a temporary file. */ if ( uname ( &utsbuf ) ) @@ -702,10 +807,17 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) } h->nodename_len = strlen (nodename); + if (h->no_write) + { + memset (h->tname, '_', tnamelen); + h->tname[tnamelen] = 0; + goto skip_write; + } + 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 ()); + "%s.%d", nodename, pid); do { @@ -722,6 +834,10 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) UNLOCK_all_lockfiles (); my_error_2 (_("failed to create temporary file '%s': %s\n"), h->tname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("failed to create temporary file '%s': %s\n"), + h->tname, strerror (errno)); xfree (h->tname); xfree (h); my_set_errno (saveerrno); @@ -755,11 +871,16 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) int saveerrno = errno; my_error_2 ("can't check whether hardlinks are supported for '%s': %s\n" , h->tname, strerror (saveerrno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_CONFIG_TEST, + "can't check whether hardlinks are supported for '%s': %s\n" + , h->tname, strerror (saveerrno)); my_set_errno (saveerrno); } goto write_failed; } + skip_write: h->lockname = xtrymalloc (strlen (file_to_lock) + 6 ); if (!h->lockname) { @@ -775,6 +896,20 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock"); UNLOCK_all_lockfiles (); + if (h->no_write) + { + if (dotlock_detect_tname (h) < 0) + { + xfree (h->lockname); + xfree (h->tname); + xfree (h); + my_set_errno (EACCES); + return NULL; + } + + h->locked = 1; + } + return h; write_failed: @@ -783,6 +918,11 @@ dotlock_create_unix (dotlock_t h, const char *file_to_lock) all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("error writing to '%s': %s\n"), h->tname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("error writing to '%s': %s\n"), + h->tname, strerror (errno)); + if ( fd != -1 ) close (fd); unlink (h->tname); @@ -849,6 +989,10 @@ dotlock_create_w32 (dotlock_t h, const char *file_to_lock) all_lockfiles = h->next; UNLOCK_all_lockfiles (); my_error_2 (_("can't create '%s': %s\n"), h->lockname, w32_strerror (-1)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("can't create '%s': %s\n"), + h->lockname, w32_strerror (-1)); xfree (h->lockname); xfree (h); my_set_errno (saveerrno); @@ -873,7 +1017,15 @@ dotlock_create_w32 (dotlock_t h, const char *file_to_lock) POSIX systems a temporary file ".#lk..pid[.threadid] is used. - FLAGS must be 0. + FLAGS may include DOTLOCK_PREPARE_CREATE bit, which only allocates + the handle and requires a further call to dotlock_finish_create. + This can be used to set a callback between these calls. + + FLAGS may include DOTLOCK_LOCK_BY_PARENT bit, when it's the parent + process controlling the lock. This is used by dotlock util. + + FLAGS may include DOTLOCK_LOCKED bit, when it should not create the + lockfile, but to unlock. This is used by dotlock util. The function returns an new handle which needs to be released using destroy_dotlock but gets also released at the termination of the @@ -885,8 +1037,13 @@ dotlock_create (const char *file_to_lock, unsigned int flags) { static int initialized; dotlock_t h; +#ifndef HAVE_DOSISH_SYSTEM + int by_parent = 0; + int no_write = 0; +#endif - if ( !initialized ) + if ( !(flags & DOTLOCK_LOCK_BY_PARENT) + && !initialized ) { atexit (dotlock_remove_lockfiles); initialized = 1; @@ -895,7 +1052,15 @@ dotlock_create (const char *file_to_lock, unsigned int flags) if ( !file_to_lock ) return NULL; /* Only initialization was requested. */ - if (flags) +#ifndef HAVE_DOSISH_SYSTEM + if ((flags & DOTLOCK_LOCK_BY_PARENT) || (flags & DOTLOCK_LOCKED)) + { + by_parent = !!(flags & DOTLOCK_LOCK_BY_PARENT); + no_write = !!(flags & DOTLOCK_LOCKED); + flags &= ~(DOTLOCK_LOCK_BY_PARENT | DOTLOCK_LOCKED); + } +#endif + if ((flags & ~DOTLOCK_PREPARE_CREATE)) { my_set_errno (EINVAL); return NULL; @@ -905,6 +1070,10 @@ dotlock_create (const char *file_to_lock, unsigned int flags) if (!h) return NULL; h->extra_fd = -1; +#ifndef HAVE_DOSISH_SYSTEM + h->by_parent = by_parent; + h->no_write = no_write; +#endif if (never_lock) { @@ -916,6 +1085,24 @@ dotlock_create (const char *file_to_lock, unsigned int flags) return h; } + if ((flags & DOTLOCK_PREPARE_CREATE)) + return h; + else + return dotlock_finish_create (h, file_to_lock); +} + + +/* This function may be used along with dotlock_create (file_name, + * DOTLOCK_PREPARE_CREATE) to finish the creation call. The given + * filename shall be the same as passed to dotlock_create. On success + * the same handle H is returned, on error NULL is returned and H is + * released. */ +dotlock_t +dotlock_finish_create (dotlock_t h, const char *file_to_lock) +{ + if (!h || !file_to_lock) + return NULL; + #ifdef HAVE_DOSISH_SYSTEM return dotlock_create_w32 (h, file_to_lock); #else /*!HAVE_DOSISH_SYSTEM */ @@ -942,6 +1129,24 @@ dotlock_get_fd (dotlock_t h) } +/* Set a callback function for info diagnostics. The callback + * function CB is called with the handle, the opaque value OPAQUE, a + * reason code, and a format string with its arguments. The callback + * shall return 0 to continue operation or true in which case the + * current function will be terminated with an error. */ +void +dotlock_set_info_cb (dotlock_t h, + int (*cb)(dotlock_t, void *, + enum dotlock_reasons reason, + const char *,...), + void *opaque) +{ + h->info_cb = cb; + h->info_cb_value = opaque; +} + + + #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of destroy_dotlock. */ @@ -952,7 +1157,6 @@ dotlock_destroy_unix (dotlock_t h) unlink (h->lockname); if (h->tname && !h->use_o_excl) unlink (h->tname); - xfree (h->tname); } #endif /*HAVE_POSIX_SYSTEM*/ @@ -998,19 +1202,74 @@ dotlock_destroy (dotlock_t h) UNLOCK_all_lockfiles (); /* Then destroy the lock. */ - if (!h->disable) + if (!h->disable + && (!h->by_parent || h->no_write)) { + /* NOTE: under the condition of (by_parent && !no_write), + it doesn't come here. So, the lock file remains. */ #ifdef HAVE_DOSISH_SYSTEM dotlock_destroy_w32 (h); #else /* !HAVE_DOSISH_SYSTEM */ dotlock_destroy_unix (h); #endif /* HAVE_DOSISH_SYSTEM */ - xfree (h->lockname); } + +#ifdef HAVE_POSIX_SYSTEM + /* When DOTLOCK_LOCK_BY_PARENT and lock fails, + the temporary file created should be removed. */ + if (h->by_parent && !h->no_write && !h->locked) + if (h->tname && !h->use_o_excl) + unlink (h->tname); + + xfree (h->tname); +#endif + xfree (h->lockname); xfree(h); } +/* Return true if H has been taken. */ +int +dotlock_is_locked (dotlock_t h) +{ + return h && !!h->locked; +} + + +/* Return the next interval to wait. WTIME and TIMEOUT are pointers + * to the current state and are updated by this function. The + * returned value might be different from the value of WTIME. */ +static int +next_wait_interval (int *wtime, long *timeout) +{ + int result; + + /* Wait until lock has been released. We use retry intervals of 4, + * 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 512, 1024, 2048ms, and + * so on. If wait-forever was requested we add a small random value + * to have different timeouts per process. */ + if (!*wtime) + *wtime = 4; + else if (*wtime < 2048) + *wtime *= 2; + else + *wtime = 512; + + result = *wtime; + if (*wtime > 8 && *timeout < 0) + result += ((unsigned int)getpid() % 37); + + if (*timeout > 0) + { + if (result > *timeout) + result = *timeout; + *timeout -= result; + } + + return result; +} + + #ifdef HAVE_POSIX_SYSTEM /* Unix specific code of make_dotlock. Returns 0 on success and -1 on @@ -1019,6 +1278,7 @@ static int dotlock_take_unix (dotlock_t h, long timeout) { int wtime = 0; + int timedout = 0; int sumtime = 0; int pid; int lastpid = -1; @@ -1047,6 +1307,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_2 ("lock not made: open(O_EXCL) of '%s' failed: %s\n", h->lockname, strerror (saveerrno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "lock not made: open(O_EXCL) of '%s' failed: %s\n", + h->lockname, strerror (saveerrno)); my_set_errno (saveerrno); return -1; } @@ -1054,7 +1318,8 @@ dotlock_take_unix (dotlock_t h, long timeout) { char pidstr[16]; - snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid()); + snprintf (pidstr, sizeof pidstr, "%10d\n", + dotlock_get_process_id (h)); if (write (fd, pidstr, 11 ) == 11 && write (fd, h->tname + h->nodename_off,h->nodename_len) == h->nodename_len @@ -1068,6 +1333,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_2 ("lock not made: writing to '%s' failed: %s\n", h->lockname, strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "lock not made: writing to '%s' failed: %s\n", + h->lockname, strerror (errno)); close (fd); unlink (h->lockname); my_set_errno (saveerrno); @@ -1086,6 +1355,10 @@ dotlock_take_unix (dotlock_t h, long timeout) saveerrno = errno; my_error_1 ("lock not made: Oops: stat of tmp file failed: %s\n", strerror (errno)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "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. */ @@ -1107,16 +1380,19 @@ dotlock_take_unix (dotlock_t h, long timeout) { saveerrno = errno; my_info_0 ("cannot read lockfile\n"); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "cannot read lockfile\n"); my_set_errno (saveerrno); return -1; } my_info_0 ("lockfile disappeared\n"); goto again; } - else if ( (pid == getpid() && same_node) + else if ( (pid == dotlock_get_process_id (h) && same_node && !h->by_parent) || (same_node && kill (pid, 0) && errno == ESRCH) ) - /* Stale lockfile is detected. */ { + /* Stale lockfile is detected. */ struct stat sb; /* Check if it's unlocked during examining the lockfile. */ @@ -1159,6 +1435,9 @@ dotlock_take_unix (dotlock_t h, long timeout) unlink (h->lockname); my_info_1 (_("removing stale lockfile (created by %d)\n"), pid); close (fd); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_STALE_REMOVED, + _("removing stale lockfile (created by %d)\n"), pid); goto again; } @@ -1170,42 +1449,39 @@ dotlock_take_unix (dotlock_t h, long timeout) if (timeout) { struct timeval tv; + int wtimereal; - /* 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 (ownerchanged) + wtime = 0; /* Reset because owner changed. */ - if (timeout > 0) - { - if (wtime > timeout) - wtime = timeout; - timeout -= wtime; - } + wtimereal = next_wait_interval (&wtime, &timeout); + if (!timeout) + timedout = 1; /* remember. */ - sumtime += wtime; + sumtime += wtimereal; if (sumtime >= 1500) { sumtime = 0; my_info_3 (_("waiting for lock (held by %d%s) %s...\n"), pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):""); + if (h->info_cb + && h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("waiting for lock (held by %d%s) %s...\n"), + pid, maybe_dead, + maybe_deadlock(h)? _("(deadlock?) "):"")) + { + my_set_errno (ECANCELED); + return -1; + } } - - tv.tv_sec = wtime / 1000; - tv.tv_usec = (wtime % 1000) * 1000; + tv.tv_sec = wtimereal / 1000; + tv.tv_usec = (wtimereal % 1000) * 1000; select (0, NULL, NULL, NULL, &tv); goto again; } - my_set_errno (EACCES); + my_set_errno (timedout? ETIMEDOUT : EACCES); return -1; } #endif /*HAVE_POSIX_SYSTEM*/ @@ -1218,6 +1494,7 @@ static int dotlock_take_w32 (dotlock_t h, long timeout) { int wtime = 0; + int timedout = 0; int w32err; OVERLAPPED ovl; @@ -1236,38 +1513,39 @@ dotlock_take_w32 (dotlock_t h, long timeout) { my_error_2 (_("lock '%s' not made: %s\n"), h->lockname, w32_strerror (w32err)); - my_set_errno (map_w32_to_errno (w32err)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + _("lock '%s' not made: %s\n"), + h->lockname, w32_strerror (w32err)); + _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; + int wtimereal; - if (timeout > 0) - { - if (wtime > timeout) - wtime = timeout; - timeout -= wtime; - } + wtimereal = next_wait_interval (&wtime, &timeout); + if (!timeout) + timedout = 1; /* remember. */ if (wtime >= 800) - my_info_1 (_("waiting for lock %s...\n"), h->lockname); + { + my_info_1 (_("waiting for lock %s...\n"), h->lockname); + if (h->info_cb + && h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING, + _("waiting for lock %s...\n"), h->lockname)) + { + my_set_errno (ECANCELED); + return -1; + } + } - Sleep (wtime); + Sleep (wtimereal); goto again; } - my_set_errno (EACCES); + my_set_errno (timedout? ETIMEDOUT : EACCES); return -1; } #endif /*HAVE_DOSISH_SYSTEM*/ @@ -1314,12 +1592,18 @@ dotlock_release_unix (dotlock_t h) { saveerrno = errno; my_error_0 ("release_dotlock: lockfile error\n"); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: lockfile error\n"); my_set_errno (saveerrno); return -1; } - if ( pid != getpid() || !same_node ) + if ( pid != dotlock_get_process_id (h) || !same_node ) { my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_CONFLICT, + "release_dotlock: not our lock (pid=%d)\n", pid); my_set_errno (EACCES); return -1; } @@ -1329,6 +1613,10 @@ dotlock_release_unix (dotlock_t h) saveerrno = errno; my_error_1 ("release_dotlock: error removing lockfile '%s'\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: error removing lockfile '%s'\n", + h->lockname); my_set_errno (saveerrno); return -1; } @@ -1349,10 +1637,15 @@ dotlock_release_w32 (dotlock_t h) memset (&ovl, 0, sizeof ovl); if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl)) { - int saveerrno = map_w32_to_errno (GetLastError ()); + int ec = (int)GetLastError (); + my_error_2 ("release_dotlock: error removing lockfile '%s': %s\n", - h->lockname, w32_strerror (-1)); - my_set_errno (saveerrno); + h->lockname, w32_strerror (ec)); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR, + "release_dotlock: error removing lockfile '%s': %s\n", + h->lockname, w32_strerror (ec)); + my_set_errno (map_w32_to_errno (ec)); return -1; } @@ -1383,6 +1676,9 @@ dotlock_release (dotlock_t h) if ( !h->locked ) { my_debug_1 ("Oops, '%s' is not locked\n", h->lockname); + if (h->info_cb) + h->info_cb (h, h->info_cb_value, DOTLOCK_NOT_LOCKED, + "Oops, '%s' is not locked\n", h->lockname); return 0; } diff --git a/common/dotlock.h b/common/dotlock.h index 03131bb0c..0f52d4037 100644 --- a/common/dotlock.h +++ b/common/dotlock.h @@ -97,12 +97,35 @@ extern "C" struct dotlock_handle; typedef struct dotlock_handle *dotlock_t; +enum dotlock_reasons + { + DOTLOCK_CONFIG_TEST, /* Can't check system - function terminates. */ + DOTLOCK_FILE_ERROR, /* General file error - function terminates. */ + DOTLOCK_INV_FILE, /* Invalid file - function terminates. */ + DOTLOCK_CONFLICT, /* Something is wrong - function terminates. */ + DOTLOCK_NOT_LOCKED, /* Not locked - No action required. */ + DOTLOCK_STALE_REMOVED, /* Stale lock file was removed - retrying. */ + DOTLOCK_WAITING /* Waiting for the lock - may be terminated. */ + }; + +/* Flags for dotlock_create. */ +#define DOTLOCK_PREPARE_CREATE (1U << 5) /* Require dotlock_finish_create. */ +#define DOTLOCK_LOCK_BY_PARENT (1U << 6) /* Used by dotlock util. */ +#define DOTLOCK_LOCKED (1U << 7) /* Used by dotlock util. */ + void dotlock_disable (void); dotlock_t dotlock_create (const char *file_to_lock, unsigned int flags); +dotlock_t dotlock_finish_create (dotlock_t h, const char *file_to_lock); void dotlock_set_fd (dotlock_t h, int fd); int dotlock_get_fd (dotlock_t h); +void dotlock_set_info_cb (dotlock_t h, + int (*cb)(dotlock_t, void *, + enum dotlock_reasons reason, + const char *,...), + void *opaque); void dotlock_destroy (dotlock_t h); int dotlock_take (dotlock_t h, long timeout); +int dotlock_is_locked (dotlock_t h); int dotlock_release (dotlock_t h); void dotlock_remove_lockfiles (void); diff --git a/common/dynload.h b/common/dynload.h index 6ac7b4e17..af3906c81 100644 --- a/common/dynload.h +++ b/common/dynload.h @@ -34,12 +34,15 @@ #ifndef __MINGW32__ # include #else -# include -# include -# include +# ifdef HAVE_WINSOCK2_H +# include +# endif +# include # include "utf8conv.h" # include "mischelp.h" -# define RTLD_LAZY 0 +# ifndef RTLD_LAZY +# define RTLD_LAZY 0 +# endif static inline void * dlopen (const char *name, int flag) diff --git a/common/exechelp-posix.c b/common/exechelp-posix.c index fa613449d..a9d91d9e7 100644 --- a/common/exechelp-posix.c +++ b/common/exechelp-posix.c @@ -84,12 +84,6 @@ my_error_from_syserror (void) return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } -static inline gpg_error_t -my_error (int errcode) -{ - return gpg_err_make (default_errsource, errcode); -} - /* Return the maximum number of currently allowed open file descriptors. Only useful on POSIX systems but returns a value on @@ -188,7 +182,7 @@ get_max_fds (void) which shall not be closed. This list shall be sorted in ascending order with the end marked by -1. */ void -close_all_fds (int first, int *except) +close_all_fds (int first, const int *except) { int max_fd = get_max_fds (); int fd, i, except_start; @@ -273,73 +267,6 @@ get_all_open_fds (void) } -/* The exec core used right after the fork. This will never return. */ -static void -do_exec (const char *pgmname, const char *argv[], - int fd_in, int fd_out, int fd_err, - int *except, unsigned int flags) -{ - char **arg_list; - int i, j; - int fds[3]; - int nodevnull[3]; - - fds[0] = fd_in; - fds[1] = fd_out; - fds[2] = fd_err; - - nodevnull[0] = !!(flags & GNUPG_SPAWN_KEEP_STDIN); - nodevnull[1] = !!(flags & GNUPG_SPAWN_KEEP_STDOUT); - nodevnull[2] = !!(flags & GNUPG_SPAWN_KEEP_STDERR); - - /* Create the command line argument array. */ - i = 0; - if (argv) - while (argv[i]) - i++; - arg_list = xcalloc (i+2, sizeof *arg_list); - arg_list[0] = strrchr (pgmname, '/'); - if (arg_list[0]) - arg_list[0]++; - else - arg_list[0] = xstrdup (pgmname); - if (argv) - for (i=0,j=1; argv[i]; i++, j++) - arg_list[j] = (char*)argv[i]; - - /* Assign /dev/null to unused FDs. */ - for (i=0; i <= 2; i++) - { - if (nodevnull[i]) - continue; - if (fds[i] == -1) - { - fds[i] = open ("/dev/null", i? O_WRONLY : O_RDONLY); - if (fds[i] == -1) - log_fatal ("failed to open '%s': %s\n", - "/dev/null", strerror (errno)); - } - } - - /* Connect the standard files. */ - for (i=0; i <= 2; i++) - { - if (nodevnull[i]) - continue; - if (fds[i] != i && dup2 (fds[i], i) == -1) - log_fatal ("dup2 std%s failed: %s\n", - i==0?"in":i==1?"out":"err", strerror (errno)); - } - - /* Close all other files. */ - close_all_fds (3, except); - - execv (pgmname, arg_list); - /* No way to print anything, as we have closed all streams. */ - _exit (127); -} - - static gpg_error_t do_create_pipe (int filedes[2]) { @@ -356,24 +283,31 @@ do_create_pipe (int filedes[2]) static gpg_error_t -create_pipe_and_estream (int filedes[2], estream_t *r_fp, +create_pipe_and_estream (gnupg_fd_t *r_fd, estream_t *r_fp, int outbound, int nonblock) { gpg_error_t err; + int filedes[2]; if (pipe (filedes) == -1) { err = my_error_from_syserror (); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - filedes[0] = filedes[1] = -1; + *r_fd = -1; *r_fp = NULL; return err; } if (!outbound) - *r_fp = es_fdopen (filedes[0], nonblock? "r,nonblock" : "r"); + { + *r_fd = filedes[1]; + *r_fp = es_fdopen (filedes[0], nonblock? "r,nonblock" : "r"); + } else - *r_fp = es_fdopen (filedes[1], nonblock? "w,nonblock" : "w"); + { + *r_fd = filedes[0]; + *r_fp = es_fdopen (filedes[1], nonblock? "w,nonblock" : "w"); + } if (!*r_fp) { err = my_error_from_syserror (); @@ -381,7 +315,7 @@ create_pipe_and_estream (int filedes[2], estream_t *r_fp, gpg_strerror (err)); close (filedes[0]); close (filedes[1]); - filedes[0] = filedes[1] = -1; + *r_fd = -1; return err; } return 0; @@ -389,36 +323,39 @@ create_pipe_and_estream (int filedes[2], estream_t *r_fp, /* Portable function to create a pipe. Under Windows the write end is - inheritable. If R_FP is not NULL, an estream is created for the - read end and stored at R_FP. */ + inheritable. Pipe is created and the read end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ gpg_error_t -gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) +gnupg_create_inbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock) { - if (r_fp) - return create_pipe_and_estream (filedes, r_fp, 0, nonblock); - else - return do_create_pipe (filedes); + if (!r_fd || !r_fp) + gpg_error (GPG_ERR_INV_ARG); + + return create_pipe_and_estream (r_fd, r_fp, 0, nonblock); } /* Portable function to create a pipe. Under Windows the read end is - inheritable. If R_FP is not NULL, an estream is created for the - write end and stored at R_FP. */ + inheritable. Pipe is created and the write end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ gpg_error_t -gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) +gnupg_create_outbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock) { - if (r_fp) - return create_pipe_and_estream (filedes, r_fp, 1, nonblock); - else - return do_create_pipe (filedes); + if (!r_fd || !r_fp) + gpg_error (GPG_ERR_INV_ARG); + + return create_pipe_and_estream (r_fd, r_fp, 1, nonblock); } -/* Portable function to create a pipe. Under Windows both ends are - inheritable. */ +/* Portable function to create a pipe. FLAGS=GNUPG_PIPE_INBOUND for + ihneritable write-end for Windows, GNUPG_PIPE_OUTBOUND for + inheritable read-end for Windows, GNUPG_PIPE_BOTH to specify + both ends may be inheritable. */ gpg_error_t -gnupg_create_pipe (int filedes[2]) +gnupg_create_pipe (int filedes[2], int flags) { + (void)flags; return do_create_pipe (filedes); } @@ -430,488 +367,3 @@ gnupg_close_pipe (int fd) if (fd != -1) close (fd); } - - -/* Fork and exec the PGMNAME, see exechelp.h for details. */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *except, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid) -{ - gpg_error_t err; - int inpipe[2] = {-1, -1}; - int outpipe[2] = {-1, -1}; - int errpipe[2] = {-1, -1}; - estream_t infp = NULL; - estream_t outfp = NULL; - estream_t errfp = NULL; - int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK); - - if (r_infp) - *r_infp = NULL; - if (r_outfp) - *r_outfp = NULL; - if (r_errfp) - *r_errfp = NULL; - *pid = (pid_t)(-1); /* Always required. */ - - if (r_infp) - { - err = create_pipe_and_estream (inpipe, &infp, 1, nonblock); - if (err) - return err; - } - - if (r_outfp) - { - err = create_pipe_and_estream (outpipe, &outfp, 0, nonblock); - if (err) - { - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - return err; - } - } - - if (r_errfp) - { - err = create_pipe_and_estream (errpipe, &errfp, 0, nonblock); - if (err) - { - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != -1) - close (outpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - - return err; - } - } - - - *pid = fork (); - if (*pid == (pid_t)(-1)) - { - err = my_error_from_syserror (); - log_error (_("error forking process: %s\n"), gpg_strerror (err)); - - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != -1) - close (outpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - - if (errfp) - es_fclose (errfp); - else if (errpipe[0] != -1) - close (errpipe[0]); - if (errpipe[1] != -1) - close (errpipe[1]); - return err; - } - - if (!*pid) - { - /* This is the child. */ - gcry_control (GCRYCTL_TERM_SECMEM); - es_fclose (infp); - es_fclose (outfp); - es_fclose (errfp); - do_exec (pgmname, argv, inpipe[0], outpipe[1], errpipe[1], - except, flags); - /*NOTREACHED*/ - } - - /* This is the parent. */ - if (inpipe[0] != -1) - close (inpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - if (errpipe[1] != -1) - close (errpipe[1]); - - if (r_infp) - *r_infp = infp; - if (r_outfp) - *r_outfp = outfp; - if (r_errfp) - *r_errfp = errfp; - - return 0; -} - - - -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process is required. - - Returns 0 on success or an error code. */ -gpg_error_t -gnupg_spawn_process_fd (const char *pgmname, const char *argv[], - int infd, int outfd, int errfd, pid_t *pid) -{ - gpg_error_t err; - - *pid = fork (); - if (*pid == (pid_t)(-1)) - { - err = my_error_from_syserror (); - log_error (_("error forking process: %s\n"), strerror (errno)); - return err; - } - - if (!*pid) - { - gcry_control (GCRYCTL_TERM_SECMEM); - /* Run child. */ - do_exec (pgmname, argv, infd, outfd, errfd, NULL, 0); - /*NOTREACHED*/ - } - - return 0; -} - - - - -/* Waiting for child processes. - - waitpid(2) may return information about terminated children that we - did not yet request, and there is no portable way to wait for a - specific set of children. - - As a workaround, we store the results of children for later use. - - XXX: This assumes that PIDs are not reused too quickly. */ - -struct terminated_child -{ - pid_t pid; - int exitcode; - struct terminated_child *next; -}; - -struct terminated_child *terminated_children; - - -static gpg_error_t -store_result (pid_t pid, int exitcode) -{ - struct terminated_child *c; - - c = xtrymalloc (sizeof *c); - if (c == NULL) - return gpg_err_code_from_syserror (); - - c->pid = pid; - c->exitcode = exitcode; - c->next = terminated_children; - terminated_children = c; - - return 0; -} - - -static int -get_result (pid_t pid, int *r_exitcode) -{ - struct terminated_child *c, **prevp; - - for (prevp = &terminated_children, c = terminated_children; - c; - prevp = &c->next, c = c->next) - if (c->pid == pid) - { - *prevp = c->next; - *r_exitcode = c->exitcode; - xfree (c); - return 1; - } - - return 0; -} - - -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) -{ - gpg_err_code_t ec; - int i, status; - - if (r_exitcode) - *r_exitcode = -1; - - if (pid == (pid_t)(-1)) - return gpg_error (GPG_ERR_INV_VALUE); - -#ifdef USE_NPTH - i = npth_waitpid (pid, &status, hang? 0:WNOHANG); -#else - while ((i=waitpid (pid, &status, hang? 0:WNOHANG)) == (pid_t)(-1) - && errno == EINTR); -#endif - - if (i == (pid_t)(-1)) - { - ec = gpg_err_code_from_errno (errno); - log_error (_("waiting for process %d to terminate failed: %s\n"), - (int)pid, strerror (errno)); - } - else if (!i) - { - ec = GPG_ERR_TIMEOUT; /* Still running. */ - } - else if (WIFEXITED (status) && WEXITSTATUS (status) == 127) - { - log_error (_("error running '%s': probably not installed\n"), pgmname); - ec = GPG_ERR_CONFIGURATION; - } - else if (WIFEXITED (status) && WEXITSTATUS (status)) - { - if (!r_exitcode) - log_error (_("error running '%s': exit status %d\n"), pgmname, - WEXITSTATUS (status)); - else - *r_exitcode = WEXITSTATUS (status); - ec = GPG_ERR_GENERAL; - } - else if (!WIFEXITED (status)) - { - log_error (_("error running '%s': terminated\n"), pgmname); - ec = GPG_ERR_GENERAL; - } - else - { - if (r_exitcode) - *r_exitcode = 0; - ec = 0; - } - - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); -} - -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, - int hang, int *r_exitcodes) -{ - gpg_err_code_t ec = 0; - size_t i, left; - int *dummy = NULL; - - if (r_exitcodes == NULL) - { - dummy = r_exitcodes = xtrymalloc (sizeof *r_exitcodes * count); - if (dummy == NULL) - return gpg_err_code_from_syserror (); - } - - for (i = 0, left = count; i < count; i++) - { - int status = -1; - - /* Skip invalid PID. */ - if (pids[i] == (pid_t)(-1)) - { - r_exitcodes[i] = -1; - left -= 1; - continue; - } - - /* See if there was a previously stored result for this pid. */ - if (get_result (pids[i], &status)) - left -= 1; - - r_exitcodes[i] = status; - } - - while (left > 0) - { - pid_t pid; - int status; - -#ifdef USE_NPTH - pid = npth_waitpid (-1, &status, hang ? 0 : WNOHANG); -#else - while ((pid = waitpid (-1, &status, hang ? 0 : WNOHANG)) == (pid_t)(-1) - && errno == EINTR); -#endif - - if (pid == (pid_t)(-1)) - { - ec = gpg_err_code_from_errno (errno); - log_error (_("waiting for processes to terminate failed: %s\n"), - strerror (errno)); - break; - } - else if (!pid) - { - ec = GPG_ERR_TIMEOUT; /* Still running. */ - break; - } - else - { - for (i = 0; i < count; i++) - if (pid == pids[i]) - break; - - if (i == count) - { - /* No match, store this result. */ - ec = store_result (pid, status); - if (ec) - break; - continue; - } - - /* Process PIDS[i] died. */ - if (r_exitcodes[i] != (pid_t) -1) - { - log_error ("PID %d was reused", pid); - ec = GPG_ERR_GENERAL; - break; - } - - left -= 1; - r_exitcodes[i] = status; - } - } - - for (i = 0; i < count; i++) - { - if (r_exitcodes[i] == -1) - continue; - - if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i]) == 127) - { - log_error (_("error running '%s': probably not installed\n"), - pgmnames[i]); - ec = GPG_ERR_CONFIGURATION; - } - else if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i])) - { - if (dummy) - log_error (_("error running '%s': exit status %d\n"), - pgmnames[i], WEXITSTATUS (r_exitcodes[i])); - else - r_exitcodes[i] = WEXITSTATUS (r_exitcodes[i]); - ec = GPG_ERR_GENERAL; - } - else if (!WIFEXITED (r_exitcodes[i])) - { - log_error (_("error running '%s': terminated\n"), pgmnames[i]); - ec = GPG_ERR_GENERAL; - } - } - - xfree (dummy); - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); -} - - - -void -gnupg_release_process (pid_t pid) -{ - (void)pid; -} - - -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. All standard file descriptors are - connected to /dev/null. */ -gpg_error_t -gnupg_spawn_process_detached (const char *pgmname, const char *argv[], - const char *envp[] ) -{ - gpg_err_code_t ec; - pid_t pid; - int i; - - if (getuid() != geteuid()) - return my_error (GPG_ERR_BUG); - - if ((ec = gnupg_access (pgmname, X_OK))) - return gpg_err_make (default_errsource, ec); - - pid = fork (); - if (pid == (pid_t)(-1)) - { - log_error (_("error forking process: %s\n"), strerror (errno)); - return my_error_from_syserror (); - } - if (!pid) - { - pid_t pid2; - - gcry_control (GCRYCTL_TERM_SECMEM); - if (setsid() == -1 || chdir ("/")) - _exit (1); - - pid2 = fork (); /* Double fork to let init take over the new child. */ - if (pid2 == (pid_t)(-1)) - _exit (1); - if (pid2) - _exit (0); /* Let the parent exit immediately. */ - - if (envp) - for (i=0; envp[i]; i++) - putenv (xstrdup (envp[i])); - - do_exec (pgmname, argv, -1, -1, -1, NULL, 0); - - /*NOTREACHED*/ - } - - if (waitpid (pid, NULL, 0) == -1) - log_error ("waitpid failed in gnupg_spawn_process_detached: %s", - strerror (errno)); - - return 0; -} - - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void -gnupg_kill_process (pid_t pid) -{ - if (pid != (pid_t)(-1)) - { - kill (pid, SIGTERM); - } -} diff --git a/common/exechelp-w32.c b/common/exechelp-w32.c index 0034e03f2..3c57e7724 100644 --- a/common/exechelp-w32.c +++ b/common/exechelp-w32.c @@ -33,6 +33,8 @@ #if !defined(HAVE_W32_SYSTEM) #error This code is only used on W32. +#else +#define _WIN32_WINNT 0x600 #endif #include @@ -66,6 +68,7 @@ #include "exechelp.h" #include +#include /* Define to 1 do enable debugging. */ #define DEBUG_W32_SPAWN 0 @@ -122,7 +125,7 @@ get_max_fds (void) /* Under Windows this is a dummy function. */ void -close_all_fds (int first, int *except) +close_all_fds (int first, const int *except) { (void)first; (void)except; @@ -181,77 +184,6 @@ get_all_open_fds (void) return array; } - -/* Helper function to build_w32_commandline. */ -static char * -build_w32_commandline_copy (char *buffer, const char *string) -{ - char *p = buffer; - const char *s; - - if (!*string) /* Empty string. */ - p = stpcpy (p, "\"\""); - else if (strpbrk (string, " \t\n\v\f\"")) - { - /* Need to do some kind of quoting. */ - p = stpcpy (p, "\""); - for (s=string; *s; s++) - { - *p++ = *s; - if (*s == '\"') - *p++ = *s; - } - *p++ = '\"'; - *p = 0; - } - else - p = stpcpy (p, string); - - return p; -} - -/* Build a command line for use with W32's CreateProcess. On success - CMDLINE gets the address of a newly allocated string. */ -static gpg_error_t -build_w32_commandline (const char *pgmname, const char * const *argv, - char **cmdline) -{ - int i, n; - const char *s; - char *buf, *p; - - *cmdline = NULL; - n = 0; - s = pgmname; - n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ - for (; *s; s++) - if (*s == '\"') - n++; /* Need to double inner quotes. */ - for (i=0; (s=argv[i]); i++) - { - n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ - for (; *s; s++) - if (*s == '\"') - n++; /* Need to double inner quotes. */ - } - n++; - - buf = p = xtrymalloc (n); - if (!buf) - return my_error_from_syserror (); - - p = build_w32_commandline_copy (p, pgmname); - for (i=0; argv[i]; i++) - { - *p++ = ' '; - p = build_w32_commandline_copy (p, argv[i]); - } - - *cmdline= buf; - return 0; -} - - #define INHERIT_READ 1 #define INHERIT_WRITE 2 #define INHERIT_BOTH (INHERIT_READ|INHERIT_WRITE) @@ -290,111 +222,130 @@ create_inheritable_pipe (HANDLE filedes[2], int flags) } -static HANDLE -w32_open_null (int for_write) -{ - HANDLE hfile; - - hfile = CreateFileW (L"nul", - for_write? GENERIC_WRITE : GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL); - if (hfile == INVALID_HANDLE_VALUE) - log_debug ("can't open 'nul': %s\n", w32_strerror (-1)); - return hfile; -} - - static gpg_error_t -create_pipe_and_estream (int filedes[2], int flags, +create_pipe_and_estream (gnupg_fd_t *r_fd, int flags, estream_t *r_fp, int outbound, int nonblock) { gpg_error_t err = 0; - HANDLE fds[2]; es_syshd_t syshd; + gnupg_fd_t fds[2]; + int inherit_flags = 0; - filedes[0] = filedes[1] = -1; - err = my_error (GPG_ERR_GENERAL); - if (!create_inheritable_pipe (fds, flags)) + if (flags == GNUPG_PIPE_OUTBOUND) + inherit_flags = INHERIT_READ; + else if (flags == GNUPG_PIPE_INBOUND) + inherit_flags = INHERIT_WRITE; + else + inherit_flags = INHERIT_BOTH; + + if (create_inheritable_pipe (fds, inherit_flags) < 0) { - filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), O_RDONLY); - if (filedes[0] == -1) - { - log_error ("failed to translate osfhandle %p\n", fds[0]); - CloseHandle (fds[1]); - } - else - { - filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), O_APPEND); - if (filedes[1] == -1) - { - log_error ("failed to translate osfhandle %p\n", fds[1]); - close (filedes[0]); - filedes[0] = -1; - CloseHandle (fds[1]); - } - else - err = 0; - } + err = my_error_from_syserror (); + log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); + *r_fd = GNUPG_INVALID_FD; + *r_fp = NULL; + return err; } - if (! err && r_fp) + syshd.type = ES_SYSHD_HANDLE; + if (!outbound) { - syshd.type = ES_SYSHD_HANDLE; - if (!outbound) - { - syshd.u.handle = fds[0]; - *r_fp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); - } - else - { - syshd.u.handle = fds[1]; - *r_fp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); - } - if (!*r_fp) - { - err = my_error_from_syserror (); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - close (filedes[0]); - close (filedes[1]); - filedes[0] = filedes[1] = -1; - return err; - } + syshd.u.handle = fds[0]; + *r_fd = fds[1]; + *r_fp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); + } + else + { + syshd.u.handle = fds[1]; + *r_fd = fds[0]; + *r_fp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); + } + if (!*r_fp) + { + err = my_error_from_syserror (); + log_error (_("error creating a stream for a pipe: %s\n"), + gpg_strerror (err)); + CloseHandle (fds[0]); + CloseHandle (fds[1]); + *r_fd = GNUPG_INVALID_FD; + return err; } - return err; + return 0; } /* Portable function to create a pipe. Under Windows the write end is - inheritable. If R_FP is not NULL, an estream is created for the - read end and stored at R_FP. */ + inheritable. Pipe is created and the read end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ gpg_error_t -gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) +gnupg_create_inbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock) { - return create_pipe_and_estream (filedes, INHERIT_WRITE, - r_fp, 0, nonblock); + if (!r_fd || !r_fp) + gpg_error (GPG_ERR_INV_ARG); + + return create_pipe_and_estream (r_fd, GNUPG_PIPE_INBOUND, r_fp, 0, nonblock); } /* Portable function to create a pipe. Under Windows the read end is - inheritable. If R_FP is not NULL, an estream is created for the - write end and stored at R_FP. */ + inheritable. Pipe is created and the write end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ gpg_error_t -gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) +gnupg_create_outbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock) { - return create_pipe_and_estream (filedes, INHERIT_READ, - r_fp, 1, nonblock); + if (!r_fd || !r_fp) + gpg_error (GPG_ERR_INV_ARG); + + return create_pipe_and_estream (r_fd, GNUPG_PIPE_OUTBOUND, r_fp, 1, nonblock); } -/* Portable function to create a pipe. Under Windows both ends are - inheritable. */ +/* Portable function to create a pipe. FLAGS=GNUPG_PIPE_INBOUND for + ihneritable write-end for Windows, GNUPG_PIPE_OUTBOUND for + inheritable read-end for Windows, GNUPG_PIPE_BOTH to specify + both ends may be inheritable. */ gpg_error_t -gnupg_create_pipe (int filedes[2]) +gnupg_create_pipe (int filedes[2], int flags) { - return create_pipe_and_estream (filedes, INHERIT_BOTH, - NULL, 0, 0); + gnupg_fd_t fds[2]; + gpg_error_t err = 0; + int inherit_flags = 0; + + if (flags == GNUPG_PIPE_OUTBOUND) + inherit_flags = INHERIT_READ; + else if (flags == GNUPG_PIPE_INBOUND) + inherit_flags = INHERIT_WRITE; + else + inherit_flags = INHERIT_BOTH; + + if (create_inheritable_pipe (fds, inherit_flags) < 0) + return my_error_from_syserror (); + + filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), O_RDONLY); + if (filedes[0] == -1) + { + log_error ("failed to translate osfhandle %p\n", fds[0]); + CloseHandle (fds[0]); + CloseHandle (fds[1]); + filedes[1] = -1; + err = my_error (GPG_ERR_GENERAL); + } + else + { + filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), O_APPEND); + if (filedes[1] == -1) + { + log_error ("failed to translate osfhandle %p\n", fds[1]); + close (filedes[0]); + filedes[0] = -1; + CloseHandle (fds[1]); + err = my_error (GPG_ERR_GENERAL); + } + else + err = 0; + } + + return err; } @@ -405,646 +356,3 @@ gnupg_close_pipe (int fd) if (fd != -1) close (fd); } - - -/* Fork and exec the PGMNAME, see exechelp.h for details. */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *except, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid) -{ - gpg_error_t err; - SECURITY_ATTRIBUTES sec_attr; - PROCESS_INFORMATION pi = - { - NULL, /* Returns process handle. */ - 0, /* Returns primary thread handle. */ - 0, /* Returns pid. */ - 0 /* Returns tid. */ - }; - STARTUPINFOW si; - int cr_flags; - char *cmdline; - wchar_t *wcmdline = NULL; - wchar_t *wpgmname = NULL; - HANDLE inpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - estream_t infp = NULL; - estream_t outfp = NULL; - estream_t errfp = NULL; - HANDLE nullhd[3] = {INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE}; - int i, rc; - es_syshd_t syshd; - gpg_err_source_t errsource = default_errsource; - int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK); - - (void)except; /* Not yet used. */ - - if (r_infp) - *r_infp = NULL; - if (r_outfp) - *r_outfp = NULL; - if (r_errfp) - *r_errfp = NULL; - *pid = (pid_t)(-1); /* Always required. */ - - if (r_infp) - { - if (create_inheritable_pipe (inpipe, INHERIT_READ)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } - - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = inpipe[1]; - infp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); - if (!infp) - { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (inpipe[0]); - CloseHandle (inpipe[1]); - inpipe[0] = inpipe[1] = INVALID_HANDLE_VALUE; - return err; - } - } - - if (r_outfp) - { - if (create_inheritable_pipe (outpipe, INHERIT_WRITE)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } - - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = outpipe[0]; - outfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); - if (!outfp) - { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (outpipe[0]); - CloseHandle (outpipe[1]); - outpipe[0] = outpipe[1] = INVALID_HANDLE_VALUE; - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - return err; - } - } - - if (r_errfp) - { - if (create_inheritable_pipe (errpipe, INHERIT_WRITE)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } - - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = errpipe[0]; - errfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); - if (!errfp) - { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (errpipe[0]); - CloseHandle (errpipe[1]); - errpipe[0] = errpipe[1] = INVALID_HANDLE_VALUE; - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - return err; - } - } - - /* Prepare security attributes. */ - memset (&sec_attr, 0, sizeof sec_attr ); - sec_attr.nLength = sizeof sec_attr; - sec_attr.bInheritHandle = FALSE; - - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; - - if (inpipe[0] == INVALID_HANDLE_VALUE) - nullhd[0] = ((flags & GNUPG_SPAWN_KEEP_STDIN)? - GetStdHandle (STD_INPUT_HANDLE) : w32_open_null (0)); - if (outpipe[1] == INVALID_HANDLE_VALUE) - nullhd[1] = ((flags & GNUPG_SPAWN_KEEP_STDOUT)? - GetStdHandle (STD_OUTPUT_HANDLE) : w32_open_null (1)); - if (errpipe[1] == INVALID_HANDLE_VALUE) - nullhd[2] = ((flags & GNUPG_SPAWN_KEEP_STDOUT)? - GetStdHandle (STD_ERROR_HANDLE) : w32_open_null (1)); - - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_HIDE; - si.hStdInput = inpipe[0] == INVALID_HANDLE_VALUE? nullhd[0] : inpipe[0]; - si.hStdOutput = outpipe[1] == INVALID_HANDLE_VALUE? nullhd[1] : outpipe[1]; - si.hStdError = errpipe[1] == INVALID_HANDLE_VALUE? nullhd[2] : errpipe[1]; - - cr_flags = (CREATE_DEFAULT_ERROR_MODE - | ((flags & GNUPG_SPAWN_DETACHED)? DETACHED_PROCESS : 0) - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_SUSPENDED); - /* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", */ - /* pgmname, cmdline); */ - /* Take care: CreateProcessW may modify wpgmname */ - if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; - else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; - else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - TRUE, /* Inherit handles. */ - cr_flags, /* Creation flags. */ - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - ); - if (!rc) - { - if (!wpgmname || !wcmdline) - log_error ("CreateProcess failed (utf8_to_wchar): %s\n", - strerror (errno)); - else - log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (errfp) - es_fclose (errfp); - else if (errpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[0]); - if (errpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[1]); - return gpg_err_make (errsource, GPG_ERR_GENERAL); - } - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - cmdline = NULL; - - /* Close the inherited handles to /dev/null. */ - for (i=0; i < DIM (nullhd); i++) - if (nullhd[i] != INVALID_HANDLE_VALUE) - CloseHandle (nullhd[i]); - - /* Close the inherited ends of the pipes. */ - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (errpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[1]); - - /* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ - /* " dwProcessID=%d dwThreadId=%d\n", */ - /* pi.hProcess, pi.hThread, */ - /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - /* log_debug (" outfp=%p errfp=%p\n", outfp, errfp); */ - - /* Fixme: For unknown reasons AllowSetForegroundWindow returns an - invalid argument error if we pass it the correct processID. As a - workaround we use -1 (ASFW_ANY). */ - if ((flags & GNUPG_SPAWN_RUN_ASFW)) - gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/); - - /* Process has been created suspended; resume it now. */ - ResumeThread (pi.hThread); - CloseHandle (pi.hThread); - - if (r_infp) - *r_infp = infp; - if (r_outfp) - *r_outfp = outfp; - if (r_errfp) - *r_errfp = errfp; - - *pid = handle_to_pid (pi.hProcess); - return 0; - -} - - - -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process is required. - - Returns 0 on success or an error code. */ -gpg_error_t -gnupg_spawn_process_fd (const char *pgmname, const char *argv[], - int infd, int outfd, int errfd, pid_t *pid) -{ - gpg_error_t err; - SECURITY_ATTRIBUTES sec_attr; - PROCESS_INFORMATION pi = { NULL, 0, 0, 0 }; - STARTUPINFOW si; - char *cmdline; - wchar_t *wcmdline = NULL; - wchar_t *wpgmname = NULL; - int i, rc; - HANDLE stdhd[3]; - - /* Setup return values. */ - *pid = (pid_t)(-1); - - /* Prepare security attributes. */ - memset (&sec_attr, 0, sizeof sec_attr ); - sec_attr.nLength = sizeof sec_attr; - sec_attr.bInheritHandle = FALSE; - - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; - - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; - stdhd[0] = infd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE; - stdhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; - stdhd[2] = errfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; - si.hStdInput = infd == -1? stdhd[0] : (void*)_get_osfhandle (infd); - si.hStdOutput = outfd == -1? stdhd[1] : (void*)_get_osfhandle (outfd); - si.hStdError = errfd == -1? stdhd[2] : (void*)_get_osfhandle (errfd); - -/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */ - /* Take care: CreateProcessW may modify wpgmname */ - if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; - else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; - else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - TRUE, /* Inherit handles. */ - (CREATE_DEFAULT_ERROR_MODE - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_SUSPENDED | DETACHED_PROCESS), - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - ); - if (!rc) - { - if (!wpgmname || !wcmdline) - log_error ("CreateProcess failed (utf8_to_wchar): %s\n", - strerror (errno)); - else - log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); - err = my_error (GPG_ERR_GENERAL); - } - else - err = 0; - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - for (i=0; i < 3; i++) - if (stdhd[i] != INVALID_HANDLE_VALUE) - CloseHandle (stdhd[i]); - if (err) - return err; - -/* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ -/* " dwProcessID=%d dwThreadId=%d\n", */ -/* pi.hProcess, pi.hThread, */ -/* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - - /* Process has been created suspended; resume it now. */ - ResumeThread (pi.hThread); - CloseHandle (pi.hThread); - - *pid = handle_to_pid (pi.hProcess); - return 0; - -} - - -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) -{ - return gnupg_wait_processes (&pgmname, &pid, 1, hang, r_exitcode); -} - -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, - int hang, int *r_exitcodes) -{ - gpg_err_code_t ec = 0; - size_t i; - HANDLE *procs; - int code; - - procs = xtrycalloc (count, sizeof *procs); - if (procs == NULL) - return my_error_from_syserror (); - - for (i = 0; i < count; i++) - { - if (r_exitcodes) - r_exitcodes[i] = -1; - - if (pids[i] == (pid_t)(-1)) - return my_error (GPG_ERR_INV_VALUE); - - procs[i] = pid_to_handle (pids[i]); - } - - /* FIXME: We should do a pth_waitpid here. However this has not yet - been implemented. A special W32 pth system call would even be - better. */ - code = WaitForMultipleObjects (count, procs, TRUE, hang? INFINITE : 0); - switch (code) - { - case WAIT_TIMEOUT: - ec = GPG_ERR_TIMEOUT; - goto leave; - - case WAIT_FAILED: - log_error (_("waiting for processes to terminate failed: %s\n"), - w32_strerror (-1)); - ec = GPG_ERR_GENERAL; - goto leave; - - case WAIT_OBJECT_0: - for (i = 0; i < count; i++) - { - DWORD exc; - - if (! GetExitCodeProcess (procs[i], &exc)) - { - log_error (_("error getting exit code of process %d: %s\n"), - (int) pids[i], w32_strerror (-1) ); - ec = GPG_ERR_GENERAL; - } - else if (exc) - { - if (!r_exitcodes) - log_error (_("error running '%s': exit status %d\n"), - pgmnames[i], (int)exc); - else - r_exitcodes[i] = (int)exc; - ec = GPG_ERR_GENERAL; - } - else - { - if (r_exitcodes) - r_exitcodes[i] = 0; - } - } - break; - - default: - log_error ("WaitForMultipleObjects returned unexpected " - "code %d\n", code); - ec = GPG_ERR_GENERAL; - break; - } - - leave: - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); -} - - - -void -gnupg_release_process (pid_t pid) -{ - if (pid != (pid_t)INVALID_HANDLE_VALUE) - { - HANDLE process = (HANDLE)pid; - - CloseHandle (process); - } -} - - -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. All standard file descriptors are - connected to /dev/null. */ -gpg_error_t -gnupg_spawn_process_detached (const char *pgmname, const char *argv[], - const char *envp[] ) -{ - gpg_error_t err; - SECURITY_ATTRIBUTES sec_attr; - PROCESS_INFORMATION pi = - { - NULL, /* Returns process handle. */ - 0, /* Returns primary thread handle. */ - 0, /* Returns pid. */ - 0 /* Returns tid. */ - }; - STARTUPINFOW si; - int cr_flags; - char *cmdline; - wchar_t *wcmdline = NULL; - wchar_t *wpgmname = NULL; - BOOL in_job = FALSE; - gpg_err_code_t ec; - int rc; - int jobdebug; - - /* We don't use ENVP. */ - (void)envp; - - cmdline = getenv ("GNUPG_EXEC_DEBUG_FLAGS"); - jobdebug = (cmdline && (atoi (cmdline) & 1)); - - if ((ec = gnupg_access (pgmname, X_OK))) - return gpg_err_make (default_errsource, ec); - - /* Prepare security attributes. */ - memset (&sec_attr, 0, sizeof sec_attr ); - sec_attr.nLength = sizeof sec_attr; - sec_attr.bInheritHandle = FALSE; - - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; - - /* Start the process. */ - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; - - cr_flags = (CREATE_DEFAULT_ERROR_MODE - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_NEW_PROCESS_GROUP - | DETACHED_PROCESS); - - /* Check if we were spawned as part of a Job. - * In a job we need to add CREATE_BREAKAWAY_FROM_JOB - * to the cr_flags, otherwise our child processes - * are killed when we terminate. */ - if (!IsProcessInJob (GetCurrentProcess(), NULL, &in_job)) - { - log_error ("IsProcessInJob() failed: %s\n", w32_strerror (-1)); - in_job = FALSE; - } - - if (in_job) - { - /* Only try to break away from job if it is allowed, otherwise - * CreateProcess() would fail with an "Access is denied" error. */ - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - if (!QueryInformationJobObject (NULL, JobObjectExtendedLimitInformation, - &info, sizeof info, NULL)) - { - log_error ("QueryInformationJobObject() failed: %s\n", - w32_strerror (-1)); - } - else if ((info.BasicLimitInformation.LimitFlags & - JOB_OBJECT_LIMIT_BREAKAWAY_OK)) - { - if (jobdebug) - log_debug ("Using CREATE_BREAKAWAY_FROM_JOB flag\n"); - cr_flags |= CREATE_BREAKAWAY_FROM_JOB; - } - else if ((info.BasicLimitInformation.LimitFlags & - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)) - { - /* The child process should automatically detach from the job. */ - if (jobdebug) - log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag; " - "JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK is set\n"); - } - else - { - /* It seems that the child process must remain in the job. - * This is not necessarily an error, although it can cause premature - * termination of the child process when the job is closed. */ - if (jobdebug) - log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag\n"); - } - } - else - { - if (jobdebug) - log_debug ("Process is not in a Job\n"); - } - - /* log_debug ("CreateProcess(detached), path='%s' cmdline='%s'\n", */ - /* pgmname, cmdline); */ - /* Take care: CreateProcessW may modify wpgmname */ - if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; - else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; - else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - FALSE, /* Inherit handles. */ - cr_flags, /* Creation flags. */ - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - ); - if (!rc) - { - if (!wpgmname || !wcmdline) - log_error ("CreateProcess failed (utf8_to_wchar): %s\n", - strerror (errno)); - else - log_error ("CreateProcess(detached) failed: %s\n", w32_strerror (-1)); - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - return my_error (GPG_ERR_GENERAL); - } - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - cmdline = NULL; - -/* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */ -/* " dwProcessID=%d dwThreadId=%d\n", */ -/* pi.hProcess, pi.hThread, */ -/* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - - CloseHandle (pi.hThread); - CloseHandle (pi.hProcess); - - return 0; -} - - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void -gnupg_kill_process (pid_t pid) -{ - if (pid != (pid_t) INVALID_HANDLE_VALUE) - { - HANDLE process = (HANDLE) pid; - - /* Arbitrary error code. */ - TerminateProcess (process, 1); - } -} diff --git a/common/exechelp.h b/common/exechelp.h index 3343fe598..10127859f 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -42,7 +42,7 @@ int get_max_fds (void); EXCEPT is not NULL, it is expected to be a list of file descriptors which are not to close. This list shall be sorted in ascending order with its end marked by -1. */ -void close_all_fds (int first, int *except); +void close_all_fds (int first, const int *except); /* Returns an array with all currently open file descriptors. The end @@ -54,159 +54,28 @@ int *get_all_open_fds (void); /* Portable function to create a pipe. Under Windows the write end is - inheritable. If R_FP is not NULL, an estream is created for the - write end and stored at R_FP. */ -gpg_error_t gnupg_create_inbound_pipe (int filedes[2], + inheritable. Pipe is created and the read end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ +gpg_error_t gnupg_create_inbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock); /* Portable function to create a pipe. Under Windows the read end is - inheritable. If R_FP is not NULL, an estream is created for the - write end and stored at R_FP. */ -gpg_error_t gnupg_create_outbound_pipe (int filedes[2], + inheritable. Pipe is created and the write end is stored at R_FD. + An estream is created for the write end and stored at R_FP. */ +gpg_error_t gnupg_create_outbound_pipe (gnupg_fd_t *r_fd, estream_t *r_fp, int nonblock); -/* Portable function to create a pipe. Under Windows both ends are - inheritable. */ -gpg_error_t gnupg_create_pipe (int filedes[2]); - -/* Close the end of a pipe. */ -void gnupg_close_pipe (int fd); - - -#define GNUPG_SPAWN_NONBLOCK 16 -#define GNUPG_SPAWN_RUN_ASFW 64 -#define GNUPG_SPAWN_DETACHED 128 -#define GNUPG_SPAWN_KEEP_STDIN 256 -#define GNUPG_SPAWN_KEEP_STDOUT 512 -#define GNUPG_SPAWN_KEEP_STDERR 1024 - -/* Fork and exec the program PGMNAME. - - If R_INFP is NULL connect stdin of the new process to /dev/null; if - it is not NULL store the address of a pointer to a new estream - there. If R_OUTFP is NULL connect stdout of the new process to - /dev/null; if it is not NULL store the address of a pointer to a - new estream there. If R_ERRFP is NULL connect stderr of the new - process to /dev/null; if it is not NULL store the address of a - pointer to a new estream there. On success the pid of the new - process is stored at PID. On error -1 is stored at PID and if - R_OUTFP or R_ERRFP are not NULL, NULL is stored there. - - The arguments for the process are expected in the NULL terminated - array ARGV. The program name itself should not be included there. - If PREEXEC is not NULL, the given function will be called right - before the exec. - - IF EXCEPT is not NULL, it is expected to be an ordered list of file - descriptors, terminated by an entry with the value (-1). These - file descriptors won't be closed before spawning a new program. - - Returns 0 on success or an error code. Calling gnupg_wait_process - and gnupg_release_process is required if the function succeeded. - - FLAGS is a bit vector: - - GNUPG_SPAWN_NONBLOCK - If set the two output streams are created in non-blocking - mode and the input stream is switched to non-blocking mode. - This is merely a convenience feature because the caller - could do the same with gpgrt_set_nonblock. Does not yet - work for Windows. - - GNUPG_SPAWN_DETACHED - If set the process will be started as a background process. - This flag is only useful under W32 (but not W32CE) systems, - so that no new console is created and pops up a console - window when starting the server. Does not work on W32CE. - - GNUPG_SPAWN_RUN_ASFW - On W32 (but not on W32CE) run AllowSetForegroundWindow for - the child. Note that due to unknown problems this actually - allows SetForegroundWindow for all children of this process. - - GNUPG_SPAWN_KEEP_STDIN - GNUPG_SPAWN_KEEP_STDOUT - GNUPG_SPAWN_KEEP_STDERR - Do not assign /dev/null to a non-required standard file - descriptor. - - */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *execpt, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid); - - -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process and - gnupg_release_process is required. Returns 0 on success or an - error code. */ -gpg_error_t gnupg_spawn_process_fd (const char *pgmname, - const char *argv[], - int infd, int outfd, int errfd, - pid_t *pid); - - -/* If HANG is true, waits for the process identified by PID to exit; - if HANG is false, checks whether the process has terminated. - PGMNAME should be the same as supplied to the spawn function and is - only used for diagnostics. Return values: - - 0 - The process exited successful. 0 is stored at R_EXITCODE. - - GPG_ERR_GENERAL - The process exited without success. The exit code of process - is then stored at R_EXITCODE. An exit code of -1 indicates - that the process terminated abnormally (e.g. due to a signal). - - GPG_ERR_TIMEOUT - The process is still running (returned only if HANG is false). - - GPG_ERR_INV_VALUE - An invalid PID has been specified. - - Other error codes may be returned as well. Unless otherwise noted, - -1 will be stored at R_EXITCODE. R_EXITCODE may be passed as NULL - if the exit code is not required (in that case an error message will - be printed). Note that under Windows PID is not the process id but - the handle of the process. */ -gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, - int *r_exitcode); - -/* Like gnupg_wait_process, but for COUNT processes. */ -gpg_error_t gnupg_wait_processes (const char **pgmnames, pid_t *pids, - size_t count, int hang, int *r_exitcodes); - - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void gnupg_kill_process (pid_t pid); - -/* Release the process identified by PID. This function is actually - only required for Windows but it does not harm to always call it. - It is a nop if PID is invalid. */ -void gnupg_release_process (pid_t pid); - - -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. */ -gpg_error_t gnupg_spawn_process_detached (const char *pgmname, - const char *argv[], - const char *envp[] ); - +enum { + GNUPG_PIPE_DONTCARE=0, + GNUPG_PIPE_INBOUND=1, + GNUPG_PIPE_OUTBOUND=2, + GNUPG_PIPE_BOTH=3 +}; +/* Portable function to create a pipe. FLAGS=GNUPG_PIPE_INBOUND for + ihneritable write-end for Windows, GNUPG_PIPE_OUTBOUND for + inheritable read-end for Windows, GNUPG_PIPE_BOTH to specify + both ends may be inheritable. */ +gpg_error_t gnupg_create_pipe (int filedes[2], int flags); #endif /*GNUPG_COMMON_EXECHELP_H*/ diff --git a/common/exectool.c b/common/exectool.c index aaf5898d0..6d22e29f8 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -38,12 +38,13 @@ #include #include + #include "i18n.h" #include "logging.h" #include "membuf.h" #include "mischelp.h" -#include "exechelp.h" #include "sysutils.h" +#include "exechelp.h" #include "util.h" #include "exectool.h" @@ -301,7 +302,6 @@ copy_buffer_flush (struct copy_buffer *c, estream_t sink) } - /* 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 @@ -321,13 +321,17 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], void *status_cb_value) { gpg_error_t err; - pid_t pid = (pid_t) -1; + gpgrt_process_t proc = NULL; estream_t infp = NULL; estream_t extrafp = NULL; estream_t outfp = NULL, errfp = NULL; es_poll_t fds[4]; +#ifdef HAVE_W32_SYSTEM + HANDLE exceptclose[2]; +#else int exceptclose[2]; - int extrapipe[2] = {-1, -1}; +#endif + gnupg_fd_t extrapipe = GNUPG_INVALID_FD; char extrafdbuf[20]; const char *argsave = NULL; int argsaveidx; @@ -335,7 +339,8 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; int quiet = 0; - int dummy_exitcode; + gpgrt_spawn_actions_t act = NULL; + int i = 0; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); @@ -382,23 +387,28 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], if (inextra) { - err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1); + 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; + /* Do not close in child. */ + exceptclose[i] = extrapipe; /* 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 +# ifdef _WIN64 + snprintf (extrafdbuf, sizeof extrafdbuf, "-&%llu", + (unsigned long long)exceptclose[i]); +# else snprintf (extrafdbuf, sizeof extrafdbuf, "-&%lu", - (unsigned long)_get_osfhandle (extrapipe[0])); + (unsigned long)exceptclose[i]); +# endif #else - snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]); + snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", exceptclose[i]); #endif for (argsaveidx=0; argv[argsaveidx]; argsaveidx++) if (!strcmp (argv[argsaveidx], "-&@INEXTRA@")) @@ -407,16 +417,34 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], argv[argsaveidx] = extrafdbuf; break; } + i++; } - else - exceptclose[0] = -1; - err = gnupg_spawn_process (pgmname, argv, - exceptclose, GNUPG_SPAWN_NONBLOCK, - input? &infp : NULL, - &outfp, &errfp, &pid); - if (extrapipe[0] != -1) - close (extrapipe[0]); + exceptclose[i] = GNUPG_INVALID_FD; + + err = gpgrt_spawn_actions_new (&act); + if (err) + goto leave; + +#ifdef HAVE_W32_SYSTEM + gpgrt_spawn_actions_set_inherit_handles (act, exceptclose); +#else + gpgrt_spawn_actions_set_inherit_fds (act, exceptclose); +#endif + err = gpgrt_process_spawn (pgmname, argv, + ((input + ? GPGRT_PROCESS_STDIN_PIPE + : 0) + | GPGRT_PROCESS_STDOUT_PIPE + | GPGRT_PROCESS_STDERR_PIPE), act, &proc); + gpgrt_process_get_streams (proc, GPGRT_PROCESS_STREAM_NONBLOCK, + input? &infp : NULL, &outfp, &errfp); + if (extrapipe != GNUPG_INVALID_FD) +#ifdef HAVE_W32_SYSTEM + CloseHandle (extrapipe); +#else + close (extrapipe); +#endif if (argsave) argv[argsaveidx] = argsave; if (err) @@ -456,6 +484,13 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], log_debug ("unexpected timeout while polling '%s'\n", pgmname); break; } + for (i=0; i < 4; i++) + if (!fds[i].ignore && fds[i].got_nval) + { + /* This should never happen. */ + log_debug ("closed fd passed to poll at idx %d - ignored\n", i); + fds[i].ignore = 1; + } if (fds[0].got_write) { @@ -546,20 +581,26 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], 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); + err = gpgrt_process_wait (proc, 1); + if (!err) + { /* To be compatible to old wait_process. */ + int status; + + gpgrt_process_ctl (proc, GPGRT_PROCESS_GET_EXIT_ID, &status); + if (status) + err = gpg_error (GPG_ERR_GENERAL); + } leave: - if (err && pid != (pid_t) -1) - gnupg_kill_process (pid); + if (err && proc) + gpgrt_process_terminate (proc); 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); + gpgrt_process_release (proc); + gpgrt_spawn_actions_release (act); copy_buffer_shred (cpbuf_in); xfree (cpbuf_in); diff --git a/common/get-passphrase.c b/common/get-passphrase.c index c24b40e88..8ea822710 100644 --- a/common/get-passphrase.c +++ b/common/get-passphrase.c @@ -94,7 +94,8 @@ start_agent (void) agentargs.lc_ctype, agentargs.lc_messages, agentargs.session_env, - 1, agentargs.verbosity, 0, NULL, NULL); + ASSHELP_FLAG_AUTOSTART, + agentargs.verbosity, 0, NULL, NULL); if (!err) { /* Tell the agent that we support Pinentry notifications. No diff --git a/common/gettime.c b/common/gettime.c index 2a9b71779..180f388bb 100644 --- a/common/gettime.c +++ b/common/gettime.c @@ -37,6 +37,11 @@ #ifdef HAVE_LANGINFO_H #include #endif +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#endif /*!HAVE_W32_SYSTEM*/ +#include /* We use uint64_t. */ #include "util.h" #include "i18n.h" @@ -61,6 +66,111 @@ static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode; #define JD_DIFF 1721060L + +/* + timegm() is a GNU function that might not be available everywhere. + It's basically the inverse of gmtime() - you give it a struct tm, + and get back a time_t. It differs from mktime() in that it handles + the case where the struct tm is UTC and the local environment isn't. + + Note, that this replacement implementation might not be thread-safe! + + Some BSDs don't handle the putenv("foo") case properly, so we use + unsetenv if the platform has it to remove environment variables. +*/ +#ifndef HAVE_TIMEGM +time_t +timegm (struct tm *tm) +{ +#ifdef HAVE_W32_SYSTEM + uint64_t val = timegm_u64 (tm); + if (val == (uint64_t)(-1)) + return (time_t)(-1); + return (time_t)val; +#else /* (Non thread safe implementation!) */ + + time_t answer; + char *zone; + + zone=getenv("TZ"); + putenv("TZ=UTC"); + tzset(); + answer=mktime(tm); + if(zone) + { + static char *old_zone; + + if (!old_zone) + { + old_zone = malloc(3+strlen(zone)+1); + if (old_zone) + { + strcpy(old_zone,"TZ="); + strcat(old_zone,zone); + } + } + if (old_zone) + putenv (old_zone); + } + else + gnupg_unsetenv("TZ"); + + tzset(); + return answer; +#endif +} +#endif /*!HAVE_TIMEGM*/ + + +/* Version of the GNU timegm which returns an unsigned 64 bit integer + * instead of the usually signed time_t. On error (uint64_t)(-1) is + * returned. This function is mostly here because on 32 bit Windows + * we have an internal API to get the system time even after + * 2023-01-19. For 32 bit Unix we need to suffer from the too short + * time_t and no system function to construct the time from a tm. */ +uint64_t +timegm_u64 (struct tm *tm) +{ +#ifdef HAVE_W32_SYSTEM + /* This one is thread safe. */ + SYSTEMTIME st; + FILETIME ft; + unsigned long long cnsecs; + + st.wYear = tm->tm_year + 1900; + st.wMonth = tm->tm_mon + 1; + st.wDay = tm->tm_mday; + st.wHour = tm->tm_hour; + st.wMinute = tm->tm_min; + st.wSecond = tm->tm_sec; + st.wMilliseconds = 0; /* Not available. */ + st.wDayOfWeek = 0; /* Ignored. */ + + /* System time is UTC thus the conversion is pretty easy. */ + if (!SystemTimeToFileTime (&st, &ft)) + { + gpg_err_set_errno (EINVAL); + return (uint64_t)(-1); + } + + cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) + | ft.dwLowDateTime); + cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ + return (uint64_t)(cnsecs / 10000000ULL); + +#else /*Unix*/ + + time_t t = timegm (tm); + if (t == (time_t)(-1)) + return (uint64_t)(-1); + if ((int64_t)t < 0) + return (uint64_t)(-1); + return (uint64_t)t; + +#endif /*Unix*/ +} + + /* Wrapper for the time(3). We use this here so we can fake the time for tests */ time_t @@ -172,6 +282,28 @@ make_timestamp (void) } +/* Specialized version of atoi which returns an u32 instead of an int + * and caps the result at 2^32-2. Leading white space is skipped, + * scanning stops at at the first non-convertable byte. Note that we + * do not cap at 2^32-1 because that value is often used as error + * return. */ +u32 +scan_secondsstr (const char *string) +{ + uint64_t value = 0; + + while (*string == ' ' || *string == '\t') + string++; + for (; *string >= '0' && *string <= '9'; string++) + { + value *= 10; + value += atoi_1 (string); + if (value >= (u32)(-1)) + return (u32)(-1) - 1; + } + return (u32)value; +} + /**************** * Scan a date string and return a timestamp. @@ -208,7 +340,21 @@ scan_isodatestr( const char *string ) tmbuf.tm_isdst = -1; stamp = mktime( &tmbuf ); if( stamp == (time_t)-1 ) - return 0; + { + /* mktime did not work. Construct an ISO timestring for noon + * of the given day instead. We keep the use of mktime for 64 + * bit system to limit the risk of regressions. */ + gnupg_isotime_t isobuf; + uint64_t tmp64; + + snprintf (isobuf, 16, "%04d%02d%02dT120000", year, month, day); + tmp64 = isotime2epoch_u64 (isobuf); + if (tmp64 == (uint64_t)(-1)) + return 0; /* Error. */ + if (tmp64 >= (u32)(-1)) + return 0; /* Error. */ + return (u32)tmp64; + } return stamp; } @@ -363,18 +509,14 @@ string2isotime (gnupg_isotime_t atime, const char *string) } -/* Scan an ISO timestamp and return an Epoch based timestamp. The - only supported format is "yyyymmddThhmmss[Z]" delimited by white - space, nul, a colon or a comma. Returns (time_t)(-1) for an - invalid string. */ -time_t -isotime2epoch (const char *string) +/* Helper for isotime2epoch. Returns 0 on success. */ +static int +isotime_make_tm (const char *string, struct tm *tmbuf) { int year, month, day, hour, minu, sec; - struct tm tmbuf; if (!isotime_p (string)) - return (time_t)(-1); + return -1; year = atoi_4 (string); month = atoi_2 (string + 4); @@ -386,20 +528,48 @@ isotime2epoch (const char *string) /* Basic checks. */ if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minu > 59 || sec > 61 ) + return -1; + + memset (tmbuf, 0, sizeof *tmbuf); + tmbuf->tm_sec = sec; + tmbuf->tm_min = minu; + tmbuf->tm_hour = hour; + tmbuf->tm_mday = day; + tmbuf->tm_mon = month-1; + tmbuf->tm_year = year - 1900; + tmbuf->tm_isdst = -1; + return 0; +} + + +/* Scan an ISO timestamp and return an Epoch based timestamp. The + only supported format is "yyyymmddThhmmss[Z]" delimited by white + space, nul, a colon or a comma. Returns (time_t)(-1) for an + invalid string. */ +time_t +isotime2epoch (const char *string) +{ + struct tm tmbuf; + + if (isotime_make_tm (string, &tmbuf)) return (time_t)(-1); - memset (&tmbuf, 0, sizeof tmbuf); - tmbuf.tm_sec = sec; - tmbuf.tm_min = minu; - tmbuf.tm_hour = hour; - tmbuf.tm_mday = day; - tmbuf.tm_mon = month-1; - tmbuf.tm_year = year - 1900; - tmbuf.tm_isdst = -1; return timegm (&tmbuf); } +uint64_t +isotime2epoch_u64 (const char *string) +{ + struct tm tmbuf; + + if (isotime_make_tm (string, &tmbuf)) + return (uint64_t)(-1); + + return timegm_u64 (&tmbuf); +} + + /* Convert an Epoch time to an iso time stamp. */ void epoch2isotime (gnupg_isotime_t timebuf, time_t atime) @@ -453,41 +623,6 @@ isodate_human_to_tm (const char *string, struct tm *t) } -/* This function is a copy of gpgme/src/conversion.c:_gpgme_timegm. - If you change it, then update the other one too. */ -#ifdef HAVE_W32_SYSTEM -static time_t -_win32_timegm (struct tm *tm) -{ - /* This one is thread safe. */ - SYSTEMTIME st; - FILETIME ft; - unsigned long long cnsecs; - - st.wYear = tm->tm_year + 1900; - st.wMonth = tm->tm_mon + 1; - st.wDay = tm->tm_mday; - st.wHour = tm->tm_hour; - st.wMinute = tm->tm_min; - st.wSecond = tm->tm_sec; - st.wMilliseconds = 0; /* Not available. */ - st.wDayOfWeek = 0; /* Ignored. */ - - /* System time is UTC thus the conversion is pretty easy. */ - if (!SystemTimeToFileTime (&st, &ft)) - { - gpg_err_set_errno (EINVAL); - return (time_t)(-1); - } - - cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) - | ft.dwLowDateTime); - cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ - return (time_t)(cnsecs / 10000000ULL); -} -#endif - - /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since @@ -496,7 +631,11 @@ _win32_timegm (struct tm *tm) This function is a copy of gpgme/src/conversion.c:_gpgme_parse_timestamp. If you change it, - then update the other one too. */ + then update the other one too. + + FIXME: Replace users of this function by one of the more modern + functions or change the return type to u64. +*/ time_t parse_timestamp (const char *timestamp, char **endp) { @@ -532,24 +671,7 @@ parse_timestamp (const char *timestamp, char **endp) buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); -#ifdef HAVE_W32_SYSTEM - return _win32_timegm (&buf); -#else -#ifdef HAVE_TIMEGM return timegm (&buf); -#else - { - time_t tim; - - putenv ("TZ=UTC"); - tim = mktime (&buf); -#ifdef __GNUC__ -#warning fixme: we must somehow reset TZ here. It is not threadsafe anyway. -#endif - return tim; - } -#endif /* !HAVE_TIMEGM */ -#endif /* !HAVE_W32_SYSTEM */ } else return (time_t)strtoul (timestamp, endp, 10); @@ -728,7 +850,7 @@ asctimestamp (u32 stamp) * 2018 has a lot of additional support but that will for sure * break other things. We should move to ISO strings to get * rid of such problems. */ - setlocale (LC_TIME, ""); + setlocale (LC_TIME, ".UTF8"); done = 1; /* log_debug ("LC_ALL now '%s'\n", setlocale (LC_ALL, NULL)); */ /* log_debug ("LC_TIME now '%s'\n", setlocale (LC_TIME, NULL)); */ diff --git a/common/gettime.h b/common/gettime.h index 4f7199f92..e216ddd36 100644 --- a/common/gettime.h +++ b/common/gettime.h @@ -32,7 +32,7 @@ #include /* We need time_t. */ #include /* We need gpg_error_t. */ - +#include /* We use uint64_t. */ /* A type to hold the ISO time. Note that this is the same as the KSBA type ksba_isotime_t. */ @@ -43,6 +43,11 @@ typedef char gnupg_isotime_t[16]; #define GNUPG_ISOTIME_NONE \ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +#ifndef HAVE_TIMEGM +time_t timegm (struct tm *tm); +#endif /*!HAVE_TIMEGM*/ +uint64_t timegm_u64 (struct tm *tm); + time_t gnupg_get_time (void); struct tm *gnupg_gmtime (const time_t *timep, struct tm *result); void gnupg_get_isotime (gnupg_isotime_t timebuf); @@ -51,11 +56,13 @@ int gnupg_faked_time_p (void); u32 make_timestamp (void); char *elapsed_time_string (time_t since, time_t now); +u32 scan_secondsstr (const char *string); u32 scan_isodatestr (const char *string); int isotime_p (const char *string); int isotime_human_p (const char *string, int date_only); size_t string2isotime (gnupg_isotime_t atime, const char *string); time_t isotime2epoch (const char *string); +uint64_t isotime2epoch_u64 (const char *string); void epoch2isotime (gnupg_isotime_t timebuf, time_t atime); int isodate_human_to_tm (const char *string, struct tm *t); time_t parse_timestamp (const char *timestamp, char **endp); diff --git a/common/helpfile.c b/common/helpfile.c index 7a7a2358a..240562794 100644 --- a/common/helpfile.c +++ b/common/helpfile.c @@ -1,5 +1,6 @@ /* helpfile.c - GnuPG's helpfile feature * Copyright (C) 2007 Free Software Foundation, Inc. + * Copyright (C) 2011,2012, 2025 g10 Code GmbH * * This file is part of GnuPG. * @@ -25,6 +26,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include @@ -38,7 +40,7 @@ /* Try to find KEY in the file FNAME. */ static char * -findkey_fname (const char *key, const char *fname) +findkey_fname (const char *key, const char *fname, unsigned int flags) { gpg_error_t err = 0; estream_t fp; @@ -126,7 +128,10 @@ findkey_fname (const char *key, const char *fname) if (is_membuf_ready (&mb)) { put_membuf_str (&mb, p); - put_membuf (&mb, "\n", 1); + if ((flags & GET_TEMPLATE_CRLF)) + put_membuf (&mb, "\r\n", 2); + else + put_membuf (&mb, "\n", 1); } } @@ -159,22 +164,23 @@ findkey_fname (const char *key, const char *fname) /* Try the help files depending on the locale. */ static char * -findkey_locale (const char *key, const char *locname, - int only_current_locale, const char *dirname) +findkey_locale (const char *domain, const char *key, const char *locname, + const char *dirname, unsigned int flags) { const char *s; char *fname, *ext, *p; char *result; - fname = xtrymalloc (strlen (dirname) + 6 + strlen (locname) + 4 + 1); + fname = xtrymalloc (strlen (dirname) + 2 + + strlen (domain) + strlen (locname) + 4 + 1); if (!fname) return NULL; - ext = stpcpy (stpcpy (fname, dirname), "/help."); + ext = stpcpy (stpcpy (stpcpy (stpcpy (fname, dirname), "/"), domain), "."); /* Search with locale name and territory. ("help.LL_TT.txt") */ if (strchr (locname, '_')) { strcpy (stpcpy (ext, locname), ".txt"); - result = findkey_fname (key, fname); + result = findkey_fname (key, fname, flags); } else result = NULL; /* No territory. */ @@ -187,17 +193,17 @@ findkey_locale (const char *key, const char *locname, for (p=ext, s=locname; *s && *s != '_';) *p++ = *s++; strcpy (p, ".txt"); - result = findkey_fname (key, fname); + result = findkey_fname (key, fname, flags); } else result = NULL; } - if (!result && (!only_current_locale || !*locname) ) + if (!result && (!(flags & GET_TEMPLATE_CURRENT_LOCALE) || !*locname)) { /* Last try: Search in file without any locale info. ("help.txt") */ strcpy (ext, "txt"); - result = findkey_fname (key, fname); + result = findkey_fname (key, fname, flags); } xfree (fname); @@ -205,43 +211,48 @@ findkey_locale (const char *key, const char *locname, } -/* Return a malloced help text as identified by KEY. The system takes +/* Return a malloced text as identified by KEY. The system takes the string from an UTF-8 encoded file to be created by an administrator or as distributed with GnuPG. On a GNU or Unix system the entry is searched in these files: - /etc/gnupg/help.LL.txt - /etc/gnupg/help.txt - /usr/share/gnupg/help.LL.txt - /usr/share/gnupg/help.txt + /etc/gnupg/..txt + /etc/gnupg/.txt + /usr/share/gnupg/..txt + /usr/share/gnupg/.txt - Here LL denotes the two digit language code of the current locale. - If ONLY_CURRENT_LOCALE is set, the function won't fallback to the - english valiant ("help.txt") unless that locale has been requested. + The is either "help" or any other domain like "mail-tube". + Here denotes the two digit language code of the current + locale. If the flag bit GET_TEMPLATE_CURRENT_LOCALE is set, the + function won't fallback to the english valiant (".txt") + unless that locale has been requested. - The help file needs to be encoded in UTF-8, lines with a '#' in the + The template file needs to be encoded in UTF-8, lines with a '#' in the first column are comment lines and entirely ignored. Help keys are identified by a key consisting of a single word with a single dot as the first character. All key lines listed without any intervening lines (except for comment lines) lead to the same help - text. Lines following the key lines make up the actual hep texts. - + text. Lines following the key lines make up the actual template texts. */ - char * -gnupg_get_help_string (const char *key, int only_current_locale) +gnupg_get_template (const char *domain, const char *key, unsigned int flags, + const char *override_locale) { - static const char *locname; + static const char *locname_buffer; + const char *locname; char *result; - if (!locname) + if (override_locale && *override_locale) + locname = override_locale; + else if (!locname_buffer) { char *buffer, *p; int count = 0; const char *s = gnupg_messages_locale_name (); + buffer = xtrystrdup (s); if (!buffer) - locname = ""; + locname_buffer = ""; else { for (p = buffer; *p; p++) @@ -250,23 +261,45 @@ gnupg_get_help_string (const char *key, int only_current_locale) else if (*p == '_') { if (count++) - *p = 0; /* Also cut at a underscore in the territory. */ + *p = 0; /* Also cut at an underscore in the territory. */ } - locname = buffer; + locname_buffer = buffer; } + locname = locname_buffer; } + else + locname = locname_buffer; if (!key || !*key) return NULL; - result = findkey_locale (key, locname, only_current_locale, - gnupg_sysconfdir ()); + result = findkey_locale (domain, key, locname, + gnupg_sysconfdir (), flags); if (!result) - result = findkey_locale (key, locname, only_current_locale, - gnupg_datadir ()); + result = findkey_locale (domain, key, locname, + gnupg_datadir (), flags); - if (result) + if (result && (flags & GET_TEMPLATE_SUBST_ENVVARS)) + { + char *tmp = substitute_envvars (result); + if (tmp) + { + xfree (result); + result = tmp; + } + } + + if (result && !(flags & GET_TEMPLATE_CRLF)) trim_trailing_spaces (result); return result; } + + +char * +gnupg_get_help_string (const char *key, int only_current) +{ + return gnupg_get_template ("help", key, + only_current? GET_TEMPLATE_CURRENT_LOCALE : 0, + NULL); +} diff --git a/common/homedir.c b/common/homedir.c index 286685feb..d26ddd902 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1,7 +1,7 @@ /* homedir.c - Setup the home directory. * Copyright (C) 2004, 2006, 2007, 2010 Free Software Foundation, Inc. * Copyright (C) 2013, 2016 Werner Koch - * Copyright (C) 2021 g10 Code GmbH + * Copyright (C) 2021, 2024 g10 Code GmbH * * This file is part of GnuPG. * @@ -77,6 +77,14 @@ #endif +/* Mode flags for unix_rootdir. */ +enum wantdir_values { + WANTDIR_ROOT = 0, + WANTDIR_SYSCONF, + WANTDIR_SOCKET +}; + + /* The GnuPG homedir. This is only accessed by the functions * gnupg_homedir and gnupg_set_homedir. Malloced. */ static char *the_gnupg_homedir; @@ -85,6 +93,22 @@ static char *the_gnupg_homedir; static byte non_default_homedir; +/* An object to store information taken from a gpgconf.ctl file. This + * is parsed early at startup time and never changed later. */ +static struct +{ + unsigned int checked:1; /* True if we have checked for a gpgconf.ctl. */ + unsigned int found:1; /* True if a gpgconf.ctl was found. */ + unsigned int empty:1; /* The file is empty except for comments. */ + unsigned int valid:1; /* The entries in gpgconf.ctl are valid. */ + unsigned int portable:1;/* Windows portable installation. */ + char *gnupg; /* The "gnupg" directory part. */ + char *rootdir; /* rootdir or NULL */ + char *sysconfdir; /* sysconfdir or NULL */ + char *socketdir; /* socketdir or NULL */ +} gpgconf_ctl; + + #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 @@ -111,6 +135,87 @@ static byte w32_bin_is_bin; static const char *w32_rootdir (void); #endif +/* Return the name of the gnupg dir. This is usually "gnupg". */ +static const char * +my_gnupg_dirname (void) +{ + if (gpgconf_ctl.valid && gpgconf_ctl.gnupg) + return gpgconf_ctl.gnupg; + return "gnupg"; +} + +/* Return the hardwired home directory which is not anymore so + * hardwired because it may now be modified using the gpgconf.ctl + * "gnupg" keyword. */ +static const char * +my_fixed_default_homedir (void) +{ + if (gpgconf_ctl.valid && gpgconf_ctl.gnupg) + { + static char *name; + char *p; + + if (!name) + { + name = xmalloc (strlen (GNUPG_DEFAULT_HOMEDIR) + + strlen (gpgconf_ctl.gnupg) + 1); + strcpy (name, GNUPG_DEFAULT_HOMEDIR); + p = strrchr (name, '/'); + if (p) + p++; + else + p = name; + if (*p == '.') + p++; /* Keep a leading dot. */ + strcpy (p, gpgconf_ctl.gnupg); + gpgrt_annotate_leaked_object (name); + } + return name; + } + return GNUPG_DEFAULT_HOMEDIR; +} + + + +/* Under Windows we need to modify the standard registry key with the + * "gnupg" keyword from a gpgconf.ctl. */ +#ifdef HAVE_W32_SYSTEM +const char * +gnupg_registry_dir (void) +{ + if (gpgconf_ctl.valid && gpgconf_ctl.gnupg) + { + static char *name; + char *p; + + if (!name) + { + name = xmalloc (strlen (GNUPG_REGISTRY_DIR) + + strlen (gpgconf_ctl.gnupg) + 1); + strcpy (name, GNUPG_REGISTRY_DIR); + p = strrchr (name, '\\'); + if (p) + p++; + else + p = name; + strcpy (p, gpgconf_ctl.gnupg); + if (!strncmp (p, "gnupg", 5)) + { + /* Registry keys are case-insensitive and we use a + * capitalized version of gnupg by default. So, if the + * new value starts with "gnupg" we apply the usual + * capitalization for this first part. */ + p[0] = 'G'; + p[3] = 'P'; + p[4] = 'G'; + } + gpgrt_annotate_leaked_object (name); + } + return name; + } + return GNUPG_REGISTRY_DIR; +} +#endif /*HAVE_W32_SYSTEM*/ /* This is a helper function to load and call a Windows function from @@ -187,7 +292,9 @@ create_common_conf (const char *dname) gpg_strerror (gpg_error_from_syserror ())); } } -#endif /* BUILD_WITH_KEYBOXD */ +#else /* BUILD_WITH_KEYBOXD */ + (void)dname; +#endif /* !BUILD_WITH_KEYBOXD */ } @@ -214,6 +321,10 @@ copy_dir_with_fixup (const char *newdir) { char *result = NULL; char *p; +#ifdef HAVE_W32_SYSTEM + char *p0; + const char *s; +#endif if (!*newdir) return NULL; @@ -245,6 +356,29 @@ copy_dir_with_fixup (const char *newdir) *p-- = 0; } + /* Hack to mitigate badly doubled backslashes. */ + s = result? result : newdir; + if (s[0] == '\\' && s[1] == '\\' && s[2] != '\\') + { + /* UNC (\\Servername\file) or Long UNC (\\?\Servername\file) + * Does not seem to be double quoted. */ + } + else if (strstr (s, "\\\\")) + { + /* Double quotes detected. Fold them into one because that is + * what what Windows does. This way we get a unique hash + * regardless of the number of doubled backslashes. */ + if (!result) + result = xstrdup (newdir); + for (p0=p=result; *p; p++) + { + *p0++ = *p; + while (*p == '\\' && p[1] == '\\') + p++; + } + *p0 = 0; + } + #else /*!HAVE_W32_SYSTEM*/ if (newdir[strlen (newdir)-1] == '/') @@ -289,7 +423,7 @@ standard_homedir (void) NULL, 0); if (path) { - dir = xstrconcat (path, "\\gnupg", NULL); + dir = xstrconcat (path, "\\", my_gnupg_dirname (), NULL); xfree (path); gpgrt_annotate_leaked_object (dir); @@ -300,12 +434,12 @@ standard_homedir (void) } else - dir = GNUPG_DEFAULT_HOMEDIR; + dir = my_fixed_default_homedir (); } } return dir; #else/*!HAVE_W32_SYSTEM*/ - return GNUPG_DEFAULT_HOMEDIR; + return my_fixed_default_homedir (); #endif /*!HAVE_W32_SYSTEM*/ } @@ -339,7 +473,7 @@ default_homedir (void) * warning if the homedir has been taken from the * registry. */ tmp = read_w32_registry_string (NULL, - GNUPG_REGISTRY_DIR, + gnupg_registry_dir (), "HomeDir"); if (tmp && !*tmp) { @@ -364,7 +498,7 @@ default_homedir (void) #endif /*HAVE_W32_SYSTEM*/ if (!dir || !*dir) - dir = GNUPG_DEFAULT_HOMEDIR; + dir = my_fixed_default_homedir (); else { char *p; @@ -392,6 +526,234 @@ default_homedir (void) } +/* Return true if S can be inteprtated as true. This is uised for + * keywords in gpgconf.ctl. Spaces must have been trimmed. */ +static int +string_is_true (const char *s) +{ + return (atoi (s) + || !ascii_strcasecmp (s, "yes") + || !ascii_strcasecmp (s, "true") + || !ascii_strcasecmp (s, "fact")); +} + +/* This function is used to parse the gpgconf.ctl file and set the + * information ito the gpgconf_ctl structure. This is called once + * with the full filename of gpgconf.ctl. There are two callers: One + * used on Windows and one on Unix. No error return but diagnostics + * are printed. */ +static void +parse_gpgconf_ctl (const char *fname) +{ + gpg_error_t err; + char *p; + char *line; + size_t linelen; + ssize_t length; + estream_t fp; + const char *name; + int anyitem = 0; + int ignoreall = 0; + char *gnupgval = NULL; + char *rootdir = NULL; + char *sysconfdir = NULL; + char *socketdir = NULL; + + if (gpgconf_ctl.checked) + return; /* Just in case this is called a second time. */ + gpgconf_ctl.checked = 1; + gpgconf_ctl.found = 0; + gpgconf_ctl.valid = 0; + gpgconf_ctl.empty = 0; + + if (gnupg_access (fname, F_OK)) + return; /* No gpgconf.ctl file. */ + + /* log_info ("detected '%s'\n", buffer); */ + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_info ("error opening '%s': %s\n", fname, gpg_strerror (err)); + return; + } + gpgconf_ctl.found = 1; + + line = NULL; + linelen = 0; + while ((length = es_read_line (fp, &line, &linelen, NULL)) > 0) + { + static const char *names[] = + { + "gnupg", + "rootdir", + "sysconfdir", + "socketdir", + "portable", + ".enable" + }; + int i; + size_t n; + + /* Strip NL and CR, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = 0; + trim_spaces (line); + if (*line == '#' || !*line) + continue; + anyitem = 1; + + /* Find the keyword. */ + name = NULL; + p = NULL; + for (i=0; i < DIM (names); i++) + { + n = strlen (names[i]); + if (!strncmp (line, names[i], n)) + { + while (line[n] == ' ' || line[n] == '\t') + n++; + if (line[n] == '=') + { + name = names[i]; + p = line + n + 1; + break; + } + } + } + if (!name) + continue; /* Keyword not known. */ + + trim_spaces (p); + p = substitute_envvars (p); + if (!p) + { + err = gpg_error_from_syserror (); + log_info ("error getting %s from gpgconf.ctl: %s\n", + name, gpg_strerror (err)); + } + else if (!strcmp (name, ".enable")) + { + if (string_is_true (p)) + ; /* Yes, this file shall be used. */ + else + ignoreall = 1; /* No, this file shall be ignored. */ + xfree (p); + } + else if (!strcmp (name, "gnupg")) + { + xfree (gnupgval); + gnupgval = p; + } + else if (!strcmp (name, "sysconfdir")) + { + xfree (sysconfdir); + sysconfdir = p; + } + else if (!strcmp (name, "socketdir")) + { + xfree (socketdir); + socketdir = p; + } + else if (!strcmp (name, "rootdir")) + { + xfree (rootdir); + rootdir = p; + } + else if (!strcmp (name, "portable")) + { + gpgconf_ctl.portable = string_is_true (p); + xfree (p); + } + else /* Unknown keyword. */ + xfree (p); + } + if (es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_info ("error reading '%s': %s\n", fname, gpg_strerror (err)); + ignoreall = 1; /* Force all entries to invalid. */ + } + es_fclose (fp); + xfree (line); + + if (ignoreall) + ; /* Forced error. Note that .found is still set. */ + else if (gnupgval && (!*gnupgval || strpbrk (gnupgval, "/\\"))) + { + /* We don't allow a slash or backslash in the value because our + * code assumes this is a single directory name. */ + log_info ("invalid %s '%s' specified in gpgconf.ctl\n", + "gnupg", gnupgval); + } + else if (rootdir && (!*rootdir || *rootdir != '/')) + { + log_info ("invalid %s '%s' specified in gpgconf.ctl\n", + "rootdir", rootdir); + } + else if (sysconfdir && (!*sysconfdir || *sysconfdir != '/')) + { + log_info ("invalid %s '%s' specified in gpgconf.ctl\n", + "sysconfdir", sysconfdir); + } + else if (socketdir && (!*socketdir || *socketdir != '/')) + { + log_info ("invalid %s '%s' specified in gpgconf.ctl\n", + "socketdir", socketdir); + } + else + { + if (gnupgval) + { + gpgconf_ctl.gnupg = gnupgval; + gpgrt_annotate_leaked_object (gpgconf_ctl.gnupg); + /* log_info ("want gnupg '%s'\n", dir); */ + } + if (rootdir) + { + while (*rootdir && rootdir[strlen (rootdir)-1] == '/') + rootdir[strlen (rootdir)-1] = 0; + gpgconf_ctl.rootdir = rootdir; + gpgrt_annotate_leaked_object (gpgconf_ctl.rootdir); + /* log_info ("want rootdir '%s'\n", dir); */ + } + if (sysconfdir) + { + while (*sysconfdir && sysconfdir[strlen (sysconfdir)-1] == '/') + sysconfdir[strlen (sysconfdir)-1] = 0; + gpgconf_ctl.sysconfdir = sysconfdir; + gpgrt_annotate_leaked_object (gpgconf_ctl.sysconfdir); + /* log_info ("want sysconfdir '%s'\n", sdir); */ + } + if (socketdir) + { + while (*socketdir && socketdir[strlen (socketdir)-1] == '/') + socketdir[strlen (socketdir)-1] = 0; + gpgconf_ctl.socketdir = socketdir; + gpgrt_annotate_leaked_object (gpgconf_ctl.socketdir); + /* log_info ("want socketdir '%s'\n", s2dir); */ + } + gpgconf_ctl.valid = 1; + } + + gpgconf_ctl.empty = !anyitem; + if (!gpgconf_ctl.valid) + { + /* Error reading some entries - clear them all. */ + xfree (gnupgval); + xfree (rootdir); + xfree (sysconfdir); + xfree (socketdir); + gpgconf_ctl.gnupg = NULL; + gpgconf_ctl.rootdir = NULL; + gpgconf_ctl.sysconfdir = NULL; + gpgconf_ctl.socketdir = NULL; + } +} + + + #ifdef HAVE_W32_SYSTEM /* Check whether gpgconf is installed and if so read the gpgconf.ctl file. */ @@ -404,17 +766,20 @@ check_portable_app (const char *dir) if (!gnupg_access (fname, F_OK)) { strcpy (fname + strlen (fname) - 3, "ctl"); - if (!gnupg_access (fname, F_OK)) + parse_gpgconf_ctl (fname); + if ((gpgconf_ctl.found && gpgconf_ctl.empty) + || (gpgconf_ctl.valid && gpgconf_ctl.portable)) { - /* gpgconf.ctl file found. Record this fact. */ + unsigned int flags; + + /* Classic gpgconf.ctl file found. This is a portable + * application. Note that if there are any items in that + * file we don't consider this a portable application unless + * the (later added) ".portable" keyword has also been + * seen. */ 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 */ + log_get_prefix (&flags); + log_set_prefix (NULL, (flags | GPGRT_LOG_NO_REGISTRY)); } } xfree (fname); @@ -491,27 +856,16 @@ w32_rootdir (void) * file system. If WANT_SYSCONFDIR is true the optional sysconfdir * entry is returned. */ static const char * -unix_rootdir (int want_sysconfdir) +unix_rootdir (enum wantdir_values wantdir) { - static int checked; - static char *dir; /* for the rootdir */ - static char *sdir; /* for the sysconfdir */ - - if (!checked) + if (!gpgconf_ctl.checked) { char *p; char *buffer; size_t bufsize = 256-1; int nread; gpg_error_t err; - char *line; - size_t linelen; - ssize_t length; - estream_t fp; - char *rootdir; - char *sysconfdir; const char *name; - int ignoreall = 0; for (;;) { @@ -551,7 +905,7 @@ unix_rootdir (int want_sysconfdir) if (!*buffer) { xfree (buffer); - checked = 1; + gpgconf_ctl.checked = 1; return NULL; /* Error - assume no gpgconf.ctl. */ } @@ -559,175 +913,39 @@ unix_rootdir (int want_sysconfdir) if (!p) { xfree (buffer); - checked = 1; + gpgconf_ctl.checked = 1; return NULL; /* Erroneous /proc - assume no gpgconf.ctl. */ } *p = 0; /* BUFFER has the directory. */ - if ((p = strrchr (buffer, '/'))) - { - /* Strip one part and expect the file below a bin dir. */ - *p = 0; - p = xstrconcat (buffer, "/bin/gpgconf.ctl", NULL); - xfree (buffer); - buffer = p; - } - else /* !p */ + if (!(p = strrchr (buffer, '/'))) { /* Installed in the root which is not a good idea. Assume * no gpgconf.ctl. */ xfree (buffer); - checked = 1; + gpgconf_ctl.checked = 1; return NULL; } - if (gnupg_access (buffer, F_OK)) - { - /* No gpgconf.ctl file. */ - xfree (buffer); - checked = 1; - return NULL; - } - /* log_info ("detected '%s'\n", buffer); */ - fp = es_fopen (buffer, "r"); - if (!fp) - { - err = gpg_error_from_syserror (); - log_info ("error opening '%s': %s\n", buffer, gpg_strerror (err)); - xfree (buffer); - checked = 1; - return NULL; - } - - line = NULL; - linelen = 0; - rootdir = NULL; - sysconfdir = NULL; - while ((length = es_read_line (fp, &line, &linelen, NULL)) > 0) - { - /* Strip NL and CR, if present. */ - while (length > 0 - && (line[length - 1] == '\n' || line[length - 1] == '\r')) - line[--length] = 0; - trim_spaces (line); - if (!strncmp (line, "rootdir=", 8)) - { - name = "rootdir"; - p = line + 8; - } - else if (!strncmp (line, "rootdir =", 9)) /* (What a kludge) */ - { - name = "rootdir"; - p = line + 9; - } - else if (!strncmp (line, "sysconfdir=", 11)) - { - name = "sysconfdir"; - p = line + 11; - } - else if (!strncmp (line, "sysconfdir =", 12)) /* (What a kludge) */ - { - name = "sysconfdir"; - p = line + 12; - } - else if (!strncmp (line, ".enable=", 8)) - { - name = ".enable"; - p = line + 8; - } - else if (!strncmp (line, ".enable =", 9)) - { - name = ".enable"; - p = line + 9; - } - else - continue; - trim_spaces (p); - p = substitute_envvars (p); - if (!p) - { - err = gpg_error_from_syserror (); - log_info ("error getting %s from gpgconf.ctl: %s\n", - name, gpg_strerror (err)); - } - else if (!strcmp (name, ".enable")) - { - if (atoi (p) - || !ascii_strcasecmp (p, "yes") - || !ascii_strcasecmp (p, "true") - || !ascii_strcasecmp (p, "fact")) - ; /* Yes, this file shall be used. */ - else - ignoreall = 1; /* No, this file shall be ignored. */ - xfree (p); - } - else if (!strcmp (name, "sysconfdir")) - { - xfree (sysconfdir); - sysconfdir = p; - } - else - { - xfree (rootdir); - rootdir = p; - } - } - if (es_ferror (fp)) - { - err = gpg_error_from_syserror (); - log_info ("error reading '%s': %s\n", buffer, gpg_strerror (err)); - es_fclose (fp); - xfree (buffer); - xfree (line); - xfree (rootdir); - xfree (sysconfdir); - checked = 1; - return NULL; - } - es_fclose (fp); + /* Strip one part and expect the file below a bin dir. */ + *p = 0; + p = xstrconcat (buffer, "/bin/gpgconf.ctl", NULL); + xfree (buffer); + buffer = p; + parse_gpgconf_ctl (buffer); xfree (buffer); - xfree (line); - - if (ignoreall) - { - xfree (rootdir); - xfree (sysconfdir); - sdir = dir = NULL; - } - else if (!rootdir || !*rootdir || *rootdir != '/') - { - log_info ("invalid rootdir '%s' specified in gpgconf.ctl\n", rootdir); - xfree (rootdir); - xfree (sysconfdir); - dir = NULL; - } - else if (sysconfdir && (!*sysconfdir || *sysconfdir != '/')) - { - log_info ("invalid sysconfdir '%s' specified in gpgconf.ctl\n", - sysconfdir); - xfree (rootdir); - xfree (sysconfdir); - dir = NULL; - } - else - { - while (*rootdir && rootdir[strlen (rootdir)-1] == '/') - rootdir[strlen (rootdir)-1] = 0; - dir = rootdir; - gpgrt_annotate_leaked_object (dir); - /* log_info ("want rootdir '%s'\n", dir); */ - if (sysconfdir) - { - while (*sysconfdir && sysconfdir[strlen (sysconfdir)-1] == '/') - sysconfdir[strlen (sysconfdir)-1] = 0; - sdir = sysconfdir; - gpgrt_annotate_leaked_object (sdir); - /* log_info ("want sysconfdir '%s'\n", sdir); */ - } - } - checked = 1; } - return want_sysconfdir? sdir : dir; + if (!gpgconf_ctl.valid) + return NULL; /* No valid entries in gpgconf.ctl */ + + switch (wantdir) + { + case WANTDIR_ROOT: return gpgconf_ctl.rootdir; + case WANTDIR_SYSCONF: return gpgconf_ctl.sysconfdir; + case WANTDIR_SOCKET: return gpgconf_ctl.socketdir; + } + + return NULL; /* Not reached. */ } #endif /* Unix */ @@ -873,7 +1091,7 @@ gnupg_daemon_rootdir (void) n = GetSystemDirectoryA (path, sizeof path); if (!n || n >= sizeof path) - name = xstrdup ("/"); /* Error - use the curret top dir instead. */ + name = xstrdup ("/"); /* Error - use the current top dir instead. */ else name = xstrdup (path); gpgrt_annotate_leaked_object (name); @@ -908,6 +1126,7 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) { #if defined(HAVE_W32_SYSTEM) char *name; + gpg_err_code_t ec; (void)skip_checks; @@ -919,7 +1138,7 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) if (w32_portable_app) { - name = xstrconcat (w32_rootdir (), DIRSEP_S, "gnupg", NULL); + name = xstrconcat (w32_rootdir (), DIRSEP_S, my_gnupg_dirname (), NULL); } else { @@ -930,7 +1149,7 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) NULL, 0); if (path) { - name = xstrconcat (path, "\\gnupg", NULL); + name = xstrconcat (path, "\\", my_gnupg_dirname (), NULL); xfree (path); if (gnupg_access (name, F_OK)) gnupg_mkdir (name, "-rwx"); @@ -985,7 +1204,8 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) else if (!skip_checks) { /* Try to create the directory and check again. */ - if (gnupg_mkdir (name, "-rwx")) + ec = gnupg_mkdir (name, "-rwx"); + if (ec && ec != GPG_ERR_EEXIST) *r_info |= 16; /* mkdir failed. */ else if (gnupg_stat (name, &sb)) { @@ -1038,9 +1258,12 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) }; int i; struct stat sb; - char prefix[19 + 1 + 20 + 6 + 1]; + char prefixbuffer[256]; + const char *prefix; const char *s; char *name = NULL; + const char *gnupgname = my_gnupg_dirname (); + gpg_err_code_t ec; *r_info = 0; @@ -1053,35 +1276,43 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) * 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++) + prefix = unix_rootdir (WANTDIR_SOCKET); + if (!prefix) { - 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; + /* gpgconf.ctl does not specify a directory. Check whether we + * have the usual /run/[gnupg/]user dir. */ + for (i=0; bases[i]; i++) + { + snprintf (prefixbuffer, sizeof prefixbuffer, "%s/user/%u", + bases[i], (unsigned int)getuid ()); + prefix = prefixbuffer; + 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) + strlen (gnupgname) + 2 >= sizeof prefixbuffer) + { + *r_info |= 1; /* Ooops: Buffer too short to append "/gnupg". */ + goto leave; + } + strcat (prefixbuffer, "/"); + strcat (prefixbuffer, gnupgname); } - 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. */ + /* Check whether the gnupg sub directory (or the specified directory) + * has proper permissions. */ if (stat (prefix, &sb)) { if (errno != ENOENT) @@ -1090,8 +1321,14 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) goto leave; } - /* Try to create the directory and check again. */ - if (gnupg_mkdir (prefix, "-rwx")) + /* Try to create the directory and check again. + * Here comes a possible race condition: + * stat(2) above failed by ENOENT, but another process does + * mkdir(2) before we do mkdir(2) + * So, an error with EEXIST should be handled. + */ + ec = gnupg_mkdir (prefix, "-rwx"); + if (ec && ec != GPG_ERR_EEXIST) { *r_info |= 16; /* mkdir failed. */ goto leave; @@ -1150,7 +1387,8 @@ _gnupg_socketdir_internal (int skip_checks, unsigned *r_info) else if (!skip_checks) { /* Try to create the directory and check again. */ - if (gnupg_mkdir (name, "-rwx")) + ec = gnupg_mkdir (name, "-rwx"); + if (ec && ec != GPG_ERR_EEXIST) *r_info |= 16; /* mkdir failed. */ else if (stat (prefix, &sb)) { @@ -1232,16 +1470,13 @@ gnupg_sysconfdir (void) 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); + name = xstrconcat (w32_commondir (), DIRSEP_S, "etc", DIRSEP_S, + my_gnupg_dirname (), NULL); gpgrt_annotate_leaked_object (name); } return name; #else /*!HAVE_W32_SYSTEM*/ - const char *dir = unix_rootdir (1); + const char *dir = unix_rootdir (WANTDIR_SYSCONF); if (dir) return dir; else @@ -1270,7 +1505,7 @@ gnupg_bindir (void) else return rdir; #else /*!HAVE_W32_SYSTEM*/ - rdir = unix_rootdir (0); + rdir = unix_rootdir (WANTDIR_ROOT); if (rdir) { if (!name) @@ -1297,7 +1532,7 @@ gnupg_libexecdir (void) static char *name; const char *rdir; - rdir = unix_rootdir (0); + rdir = unix_rootdir (WANTDIR_ROOT); if (rdir) { if (!name) @@ -1327,7 +1562,7 @@ gnupg_libdir (void) #else /*!HAVE_W32_SYSTEM*/ const char *rdir; - rdir = unix_rootdir (0); + rdir = unix_rootdir (WANTDIR_ROOT); if (rdir) { if (!name) @@ -1358,7 +1593,7 @@ gnupg_datadir (void) #else /*!HAVE_W32_SYSTEM*/ const char *rdir; - rdir = unix_rootdir (0); + rdir = unix_rootdir (WANTDIR_ROOT); if (rdir) { if (!name) @@ -1390,7 +1625,7 @@ gnupg_localedir (void) #else /*!HAVE_W32_SYSTEM*/ const char *rdir; - rdir = unix_rootdir (0); + rdir = unix_rootdir (WANTDIR_ROOT); if (rdir) { if (!name) @@ -1641,20 +1876,10 @@ gnupg_module_name (int which) 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); + 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"); + X(bindir, "g10", GPG_NAME "v"); case GNUPG_MODULE_NAME_CONNECT_AGENT: X(bindir, "tools", "gpg-connect-agent"); diff --git a/common/init.c b/common/init.c index 62a48f8c7..8ea51c8b0 100644 --- a/common/init.c +++ b/common/init.c @@ -37,6 +37,7 @@ # include # endif # include +# include #endif #include diff --git a/common/iobuf.c b/common/iobuf.c index 62cde27f9..7aaf3a878 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -166,7 +166,8 @@ block_filter_ctx_t; /* Local prototypes. */ static int underflow (iobuf_t a, int clear_pending_eof); static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target); -static int translate_file_handle (int fd, int for_write); +static iobuf_t do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open); + /* Sends any pending data to the filter's FILTER function. Note: this works on the filter and not on the whole pipeline. That is, @@ -311,6 +312,13 @@ direct_open (const char *fname, const char *mode, int mode700) { hfile = CreateFileW (wfname, da, sm, NULL, cd, FILE_ATTRIBUTE_NORMAL, NULL); + if (hfile == INVALID_HANDLE_VALUE) + { + gnupg_w32_set_errno (-1); + if (DBG_IOBUF) + log_debug ("iobuf:direct_open '%s' CreateFile failed: %s\n", + fname, gpg_strerror (gpg_error_from_syserror())); + } xfree (wfname); } else @@ -382,7 +390,7 @@ fd_cache_close (const char *fname, gnupg_fd_t fp) close (fp); #endif if (DBG_IOBUF) - log_debug ("fd_cache_close (%d) real\n", (int)fp); + log_debug ("fd_cache_close (%d) real\n", FD_DBG (fp)); return; } /* try to reuse a slot */ @@ -426,8 +434,9 @@ fd_cache_open (const char *fname, const char *mode) #ifdef HAVE_W32_SYSTEM if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff) { - log_error ("rewind file failed on handle %p: ec=%d\n", - fp, (int) GetLastError ()); + int ec = (int) GetLastError (); + log_error ("rewind file failed on handle %p: ec=%d\n", fp, ec); + gnupg_w32_set_errno (ec); fp = GNUPG_INVALID_FD; } #else @@ -495,7 +504,8 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf, if (ec != ERROR_BROKEN_PIPE) { rc = gpg_error_from_errno (ec); - log_error ("%s: read error: ec=%d\n", a->fname, ec); + log_error ("%s: read error: %s (ec=%d)\n", + a->fname, gpg_strerror (rc), ec); } } else if (!nread) @@ -563,9 +573,10 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf, { if (size && !WriteFile (f, p, nbytes, &n, NULL)) { - int ec = (int) GetLastError (); - rc = gpg_error_from_errno (ec); - log_error ("%s: write error: ec=%d\n", a->fname, ec); + int ec = gnupg_w32_set_errno (-1); + rc = gpg_error_from_syserror (); + log_error ("%s: write error: %s (ec=%d)\n", + a->fname, gpg_strerror (rc), ec); break; } p += n; @@ -624,7 +635,8 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf, if (ec != ERROR_BROKEN_PIPE) { rc = gpg_error_from_errno (ec); - log_error ("%s: read error: ec=%d\n", a->fname, ec); + log_error ("%s: read error: %s (ec=%d)\n", + a->fname, gpg_strerror (rc), ec); } a->npeeked = 0; } @@ -685,7 +697,7 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf, if (f != FD_FOR_STDIN && f != FD_FOR_STDOUT) { if (DBG_IOBUF) - log_debug ("%s: close fd/handle %d\n", a->fname, FD2INT (f)); + log_debug ("%s: close fd/handle %d\n", a->fname, FD_DBG (f)); if (!a->keep_open) fd_cache_close (a->no_cache ? NULL : a->fname, f); } @@ -873,7 +885,8 @@ sock_filter (void *opaque, int control, iobuf_t chain, byte * buf, if (n == SOCKET_ERROR) { int ec = (int) WSAGetLastError (); - rc = gpg_error_from_errno (ec); + gnupg_w32_set_errno (ec); + rc = gpg_error_from_syserror (); log_error ("socket write error: ec=%d\n", ec); break; } @@ -1398,7 +1411,7 @@ iobuf_is_pipe_filename (const char *fname) { if (!fname || (*fname=='-' && !fname[1]) ) return 1; - return check_special_filename (fname, 0, 1) != -1; + return gnupg_check_special_filename (fname) != GNUPG_INVALID_FD; } @@ -1411,7 +1424,7 @@ do_open (const char *fname, int special_filenames, file_filter_ctx_t *fcx; size_t len = 0; int print_only = 0; - int fd; + gnupg_fd_t fd; byte desc[MAX_IOBUF_DESC]; log_assert (use == IOBUF_INPUT || use == IOBUF_OUTPUT); @@ -1435,9 +1448,8 @@ do_open (const char *fname, int special_filenames, else if (!fname) return NULL; else if (special_filenames - && (fd = check_special_filename (fname, 0, 1)) != -1) - return iobuf_fdopen (translate_file_handle (fd, use == IOBUF_INPUT ? 0 : 1), - opentype); + && (fd = gnupg_check_special_filename (fname)) != GNUPG_INVALID_FD) + return do_iobuf_fdopen (fd, opentype, 0); else { if (use == IOBUF_INPUT) @@ -1460,7 +1472,8 @@ do_open (const char *fname, int special_filenames, file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: open '%s' desc=%s fd=%d\n", - a->no, a->subno, fname, iobuf_desc (a, desc), FD2INT (fcx->fp)); + a->no, a->subno, fname, iobuf_desc (a, desc), + FD_DBG (fcx->fp)); return a; } @@ -1485,22 +1498,19 @@ iobuf_openrw (const char *fname) static iobuf_t -do_iobuf_fdopen (int fd, const char *mode, int keep_open) +do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open) { iobuf_t a; - gnupg_fd_t fp; file_filter_ctx_t *fcx; size_t len = 0; - fp = INT2FD (fd); - a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT, iobuf_buffer_size); fcx = xmalloc (sizeof *fcx + 20); fcx->fp = fp; fcx->print_only_name = 1; fcx->keep_open = keep_open; - sprintf (fcx->fname, "[fd %d]", fd); + sprintf (fcx->fname, "[fd %d]", FD_DBG (fp)); a->filter = file_filter; a->filter_ov = fcx; file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); @@ -1513,15 +1523,15 @@ do_iobuf_fdopen (int fd, const char *mode, int keep_open) iobuf_t -iobuf_fdopen (int fd, const char *mode) +iobuf_fdopen (gnupg_fd_t fp, const char *mode) { - return do_iobuf_fdopen (fd, mode, 0); + return do_iobuf_fdopen (fp, mode, 0); } iobuf_t -iobuf_fdopen_nc (int fd, const char *mode) +iobuf_fdopen_nc (gnupg_fd_t fp, const char *mode) { - return do_iobuf_fdopen (fd, mode, 1); + return do_iobuf_fdopen (fp, mode, 1); } @@ -1573,7 +1583,7 @@ iobuf_sockopen (int fd, const char *mode) log_debug ("iobuf-%d.%d: sockopen '%s'\n", a->no, a->subno, scx->fname); iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL); #else - a = iobuf_fdopen (fd, mode); + a = do_iobuf_fdopen (fd, mode, 0); #endif return a; } @@ -1660,7 +1670,7 @@ iobuf_ioctl (iobuf_t a, iobuf_ioctl_t cmd, int intval, void *ptrval) /* Peek at a justed opened file. Use this only directly after a * file has been opened for reading. Don't use it after you did * a seek. This works only if just file filter has been - * pushed. Expects a buffer wit size INTVAL at PTRVAL and returns + * pushed. Expects a buffer with size INTVAL at PTRVAL and returns * the number of bytes put into the buffer. */ if (DBG_IOBUF) log_debug ("iobuf-%d.%d: ioctl '%s' peek\n", @@ -2597,13 +2607,10 @@ iobuf_set_limit (iobuf_t a, off_t nlimit) } - -off_t -iobuf_get_filelength (iobuf_t a, int *overflow) +/* Return the length of the file behind A. If there is no file, return 0. */ +uint64_t +iobuf_get_filelength (iobuf_t a) { - if (overflow) - *overflow = 0; - /* Hmmm: file_filter may have already been removed */ for ( ; a->chain; a = a->chain ) ; @@ -2616,56 +2623,18 @@ iobuf_get_filelength (iobuf_t a, int *overflow) gnupg_fd_t fp = b->fp; #if defined(HAVE_W32_SYSTEM) - ulong size; - static int (* __stdcall get_file_size_ex) (void *handle, - LARGE_INTEGER *r_size); - static int get_file_size_ex_initialized; + LARGE_INTEGER exsize; - if (!get_file_size_ex_initialized) - { - void *handle; - - handle = dlopen ("kernel32.dll", RTLD_LAZY); - if (handle) - { - get_file_size_ex = dlsym (handle, "GetFileSizeEx"); - if (!get_file_size_ex) - dlclose (handle); - } - get_file_size_ex_initialized = 1; - } - - if (get_file_size_ex) - { - /* This is a newer system with GetFileSizeEx; we use this - then because it seem that GetFileSize won't return a - proper error in case a file is larger than 4GB. */ - LARGE_INTEGER exsize; - - if (get_file_size_ex (fp, &exsize)) - { - if (!exsize.u.HighPart) - return exsize.u.LowPart; - if (overflow) - *overflow = 1; - return 0; - } - } - else - { - if ((size=GetFileSize (fp, NULL)) != 0xffffffff) - return size; - } + if (GetFileSizeEx (fp, &exsize)) + return exsize.QuadPart; log_error ("GetFileSize for handle %p failed: %s\n", fp, w32_strerror (-1)); #else /*!HAVE_W32_SYSTEM*/ - { - struct stat st; + struct stat st; - if ( !fstat (fp, &st) ) - return st.st_size; - log_error("fstat() failed: %s\n", strerror(errno) ); - } + if ( !fstat (fp, &st) ) + return st.st_size; + log_error("fstat() failed: %s\n", strerror(errno) ); #endif /*!HAVE_W32_SYSTEM*/ } @@ -2673,20 +2642,20 @@ iobuf_get_filelength (iobuf_t a, int *overflow) } -int +gnupg_fd_t iobuf_get_fd (iobuf_t a) { for (; a->chain; a = a->chain) ; if (a->filter != file_filter) - return -1; + return GNUPG_INVALID_FD; { file_filter_ctx_t *b = a->filter_ov; gnupg_fd_t fp = b->fp; - return FD2INT (fp); + return fp; } } @@ -2977,36 +2946,6 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, return nbytes; } -static int -translate_file_handle (int fd, int for_write) -{ -#if defined(HAVE_W32_SYSTEM) - { - int x; - - (void)for_write; - - if (fd == 0) - x = (int) GetStdHandle (STD_INPUT_HANDLE); - else if (fd == 1) - x = (int) GetStdHandle (STD_OUTPUT_HANDLE); - else if (fd == 2) - x = (int) GetStdHandle (STD_ERROR_HANDLE); - else - x = fd; - - if (x == -1) - log_debug ("GetStdHandle(%d) failed: ec=%d\n", - fd, (int) GetLastError ()); - - fd = x; - } -#else - (void)for_write; -#endif - return fd; -} - void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial) @@ -3057,3 +2996,123 @@ iobuf_skip_rest (iobuf_t a, unsigned long n, int partial) } } } + + +/* Check whether (BUF,LEN) is valid header for an OpenPGP compressed + * packet. LEN should be at least 6. */ +static int +is_openpgp_compressed_packet (const unsigned char *buf, size_t len) +{ + int c, ctb, pkttype; + int lenbytes; + + ctb = *buf++; len--; + if (!(ctb & 0x80)) + return 0; /* Invalid packet. */ + + if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */ + { + pkttype = (ctb & 0x3f); + if (!len) + return 0; /* Expected first length octet missing. */ + c = *buf++; len--; + if (c < 192) + ; + else if (c < 224) + { + if (!len) + return 0; /* Expected second length octet missing. */ + } + else if (c == 255) + { + if (len < 4) + return 0; /* Expected length octets missing */ + } + } + else /* Old style CTB. */ + { + pkttype = (ctb>>2)&0xf; + lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); + if (len < lenbytes) + return 0; /* Not enough length bytes. */ + } + + return (pkttype == 8); +} + + +/* + * Check if the file is compressed, by peeking the iobuf. You need to + * pass the iobuf with INP. Returns true if the buffer seems to be + * compressed. + */ +int +is_file_compressed (iobuf_t inp) +{ + int i; + char buf[32]; + int buflen; + + struct magic_compress_s + { + byte len; + byte extchk; + byte magic[5]; + } magic[] = + { + { 3, 0, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */ + { 3, 0, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */ + { 4, 0, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */ + { 5, 0, { '%', 'P', 'D', 'F', '-'} }, /* PDF */ + { 4, 1, { 0xff, 0xd8, 0xff, 0xe0 } }, /* Maybe JFIF */ + { 5, 2, { 0x89, 'P','N','G', 0x0d} } /* Likely PNG */ + }; + + if (!inp) + return 0; + + for ( ; inp->chain; inp = inp->chain ) + ; + + buflen = iobuf_ioctl (inp, IOBUF_IOCTL_PEEK, sizeof buf, buf); + if (buflen < 0) + { + buflen = 0; + log_debug ("peeking at input failed\n"); + } + + if ( buflen < 6 ) + { + return 0; /* Too short to check - assume uncompressed. */ + } + + for ( i = 0; i < DIM (magic); i++ ) + { + if (!memcmp( buf, magic[i].magic, magic[i].len)) + { + switch (magic[i].extchk) + { + case 0: + return 1; /* Is compressed. */ + case 1: + if (buflen > 11 && !memcmp (buf + 6, "JFIF", 5)) + return 1; /* JFIF: this likely a compressed JPEG. */ + break; + case 2: + if (buflen > 8 + && buf[5] == 0x0a && buf[6] == 0x1a && buf[7] == 0x0a) + return 1; /* This is a PNG. */ + break; + default: + break; + } + } + } + + if (buflen >= 6 && is_openpgp_compressed_packet (buf, buflen)) + { + return 1; /* Already compressed. */ + } + + return 0; /* Not detected as compressed. */ +} diff --git a/common/iobuf.h b/common/iobuf.h index c132c2f3c..5fa064c20 100644 --- a/common/iobuf.h +++ b/common/iobuf.h @@ -204,7 +204,7 @@ struct iobuf_struct byte *buf; } d; - /* A external drain buffer for reading/writting data skipping internal + /* A external drain buffer for reading/writing data skipping internal draint buffer D.BUF. This allows zerocopy operation reducing processing overhead across filter stack. @@ -333,11 +333,11 @@ iobuf_t iobuf_openrw (const char *fname); creates an input filter. Note: MODE must reflect the file descriptors actual mode! When the filter is destroyed, the file descriptor is closed. */ -iobuf_t iobuf_fdopen (int fd, const char *mode); +iobuf_t iobuf_fdopen (gnupg_fd_t fd, const char *mode); /* Like iobuf_fdopen, but doesn't close the file descriptor when the filter is destroyed. */ -iobuf_t iobuf_fdopen_nc (int fd, const char *mode); +iobuf_t iobuf_fdopen_nc (gnupg_fd_t fd, const char *mode); /* Create a filter using an existing estream. If MODE contains the letter 'w', creates an output filter. Otherwise, creates an input @@ -584,17 +584,13 @@ size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen); size_t iobuf_copy (iobuf_t dest, iobuf_t source); /* Return the size of any underlying file. This only works with - file_filter based pipelines. - - On Win32, it is sometimes not possible to determine the size of - files larger than 4GB. In this case, *OVERFLOW (if not NULL) is - set to 1. Otherwise, *OVERFLOW is set to 0. */ -off_t iobuf_get_filelength (iobuf_t a, int *overflow); + file_filter based pipelines. */ +uint64_t iobuf_get_filelength (iobuf_t a); #define IOBUF_FILELENGTH_LIMIT 0xffffffff /* Return the file descriptor designating the underlying file. This only works with file_filter based pipelines. */ -int iobuf_get_fd (iobuf_t a); +gnupg_fd_t iobuf_get_fd (iobuf_t a); /* Return the real filename, if available. This only supports pipelines that end in file filters. Returns NULL if not @@ -629,6 +625,9 @@ void iobuf_set_partial_body_length_mode (iobuf_t a, size_t len); from the following filter (which may or may not return EOF). */ void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial); +/* Check if the file is compressed, by peeking the iobuf. */ +int is_file_compressed (iobuf_t inp); + #define iobuf_where(a) "[don't know]" /* Each time a filter is allocated (via iobuf_alloc()), a diff --git a/common/kem.c b/common/kem.c new file mode 100644 index 000000000..5d994f0d6 --- /dev/null +++ b/common/kem.c @@ -0,0 +1,319 @@ +/* kem.c - KEM helper functions + * Copyright (C) 2024 g10 Code GmbH. + * + * 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 copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) + */ + +#include +#include +#include +#include +#include +#include "mischelp.h" +#include "util.h" + +/* domSeperation as per *PGP specs. */ +#define KMAC_KEY "OpenPGPCompositeKeyDerivationFunction" + +/* customizationString as per *PGP specs. */ +#define KMAC_CUSTOM "KDF" + +/* The blocksize used for Keccak by compute_kmac256. */ +#define KECCAK512_BLOCKSIZE 136 + + + +static gpg_error_t +compute_kmac256 (void *digest, size_t digestlen, + const void *key, size_t keylen, + const void *custom, size_t customlen, + gcry_buffer_t *data_iov, int data_iovlen) +{ + gpg_error_t err; + gcry_buffer_t iov[20]; + const unsigned char headPAD[2] = { 1, KECCAK512_BLOCKSIZE }; + unsigned char headK[3]; + const unsigned char pad[KECCAK512_BLOCKSIZE] = { 0 }; + unsigned char right_encode_L[3]; + unsigned int len; + int iovcnt; + + if (data_iovlen >= DIM(iov) - 6) + return gpg_error (GPG_ERR_TOO_LARGE); + + /* Check the validity conditions of NIST SP 800-185 */ + if (keylen >= 255 || customlen >= 255 || digestlen >= 255) + return gpg_error (GPG_ERR_TOO_LARGE); + + iovcnt = 0; + iov[iovcnt].data = "KMAC"; + iov[iovcnt].off = 0; + iov[iovcnt].len = 4; + iovcnt++; + + iov[iovcnt].data = (void *)custom; + iov[iovcnt].off = 0; + iov[iovcnt].len = customlen; + iovcnt++; + + iov[iovcnt].data = (void *)headPAD; + iov[iovcnt].off = 0; + iov[iovcnt].len = sizeof (headPAD); + iovcnt++; + + if (keylen < 32) + { + headK[0] = 1; + headK[1] = (keylen*8)&0xff; + iov[iovcnt].data = headK; + iov[iovcnt].off = 0; + iov[iovcnt].len = 2; + } + else + { + headK[0] = 2; + headK[1] = (keylen*8)>>8; + headK[2] = (keylen*8)&0xff; + iov[iovcnt].data = headK; + iov[iovcnt].off = 0; + iov[iovcnt].len = 3; + } + iovcnt++; + + iov[iovcnt].data = (void *)key; + iov[iovcnt].off = 0; + iov[iovcnt].len = keylen; + iovcnt++; + + len = iov[2].len + iov[3].len + iov[4].len; + len %= KECCAK512_BLOCKSIZE; + + iov[iovcnt].data = (unsigned char *)pad; + iov[iovcnt].off = 0; + iov[iovcnt].len = sizeof (pad) - len; + iovcnt++; + + memcpy (&iov[iovcnt], data_iov, data_iovlen * sizeof (gcry_buffer_t)); + iovcnt += data_iovlen; + + if (digestlen < 32) + { + right_encode_L[0] = (digestlen * 8) & 0xff; + right_encode_L[1] = 1; + } + else + { + right_encode_L[0] = (digestlen * 8) >> 8; + right_encode_L[1] = (digestlen * 8) & 0xff; + right_encode_L[2] = 2; + } + + iov[iovcnt].data = right_encode_L; + iov[iovcnt].off = 0; + iov[iovcnt].len = 3; + iovcnt++; + + err = gcry_md_hash_buffers_ext (GCRY_MD_CSHAKE256, 0, + digest, digestlen, iov, iovcnt); + return err; +} + + +/* Compute KEK for ECC with HASHALGO, ECDH result, ciphertext in + * ECC_CT (which is an ephemeral key), and public key in ECC_PK. + * + * For traditional ECC (of v4), KDF_PARAMS is specified by upper layer + * and an ephemeral key and public key are not used for the + * computation. + */ +gpg_error_t +gnupg_ecc_kem_kdf (void *kek, size_t kek_len, + int hashalgo, const void *ecdh, size_t ecdh_len, + const void *ecc_ct, size_t ecc_ct_len, + const void *ecc_pk, size_t ecc_pk_len, + unsigned char *kdf_params, size_t kdf_params_len) +{ + if (kdf_params) + { + /* Traditional ECC */ + gpg_error_t err; + gcry_kdf_hd_t hd; + unsigned long param[1]; + + param[0] = kek_len; + err = gcry_kdf_open (&hd, GCRY_KDF_ONESTEP_KDF, hashalgo, param, 1, + ecdh, ecdh_len, NULL, 0, NULL, 0, + kdf_params, kdf_params_len); + if (!err) + { + gcry_kdf_compute (hd, NULL); + gcry_kdf_final (hd, kek_len, kek); + gcry_kdf_close (hd); + } + + return err; + } + else + { + /* ECC in composite KEM */ + gcry_buffer_t iov[3]; + unsigned int dlen; + + dlen = gcry_md_get_algo_dlen (hashalgo); + if (kek_len != dlen) + return gpg_error (GPG_ERR_INV_LENGTH); + + memset (iov, 0, sizeof (iov)); + + iov[0].data = (unsigned char *)ecdh; + iov[0].len = ecdh_len; + iov[1].data = (unsigned char *)ecc_ct; + iov[1].len = ecc_ct_len; + iov[2].data = (unsigned char *)ecc_pk; + iov[2].len = ecc_pk_len; + gcry_md_hash_buffers (hashalgo, 0, kek, iov, 3); + } + + return 0; +} + +/* Compute KEK by combining two KEMs. The caller provides a buffer + * KEK allocated with size KEK_LEN which will receive the computed + * KEK. (ECC_SS, ECC_SS_LEN) is the shared secret of the first key. + * (ECC_CT, ECC_CT_LEN) is the ciphertext of the first key. + * (MLKEM_SS, ECC_SS_LEN) is the shared secret of the second key. + * (MLKEM_CT, MLKEM_CT_LEN) is the ciphertext of the second key. + * (FIXEDINFO, FIXEDINFO_LEN) is an octet string used to bind the KEK + * to a the key; for PGP we use the concatenation of the session key's + * algorithm id and the v5 fingerprint of the key. + */ +gpg_error_t +gnupg_kem_combiner (void *kek, size_t kek_len, + const void *ecc_ss, size_t ecc_ss_len, + const void *ecc_ct, size_t ecc_ct_len, + const void *mlkem_ss, size_t mlkem_ss_len, + const void *mlkem_ct, size_t mlkem_ct_len, + const void *fixedinfo, size_t fixedinfo_len) +{ + gpg_error_t err; + gcry_buffer_t iov[6]; + + memset (iov, 0, sizeof (iov)); + + iov[0].data = "\x00\x00\x00\x01"; /* Counter */ + iov[0].len = 4; + + iov[1].data = (unsigned char *)ecc_ss; + iov[1].len = ecc_ss_len; + + iov[2].data = (unsigned char *)ecc_ct; + iov[2].len = ecc_ct_len; + + iov[3].data = (unsigned char *)mlkem_ss; + iov[3].len = mlkem_ss_len; + + iov[4].data = (unsigned char *)mlkem_ct; + iov[4].len = mlkem_ct_len; + + iov[5].data = (unsigned char *)fixedinfo; + iov[5].len = fixedinfo_len; + + err = compute_kmac256 (kek, kek_len, + KMAC_KEY, strlen (KMAC_KEY), + KMAC_CUSTOM, strlen (KMAC_CUSTOM), iov, 6); + return err; +} + +#define ECC_CURVE25519_INDEX 0 +static const struct gnupg_ecc_params ecc_table[] = + { + { + "Curve25519", + 33, 32, 32, + GCRY_MD_SHA3_256, GCRY_KEM_RAW_X25519, + 1, 1 + }, + { + "X448", + 56, 56, 56, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_X448, + 0, 0 + }, + { + "NIST P-256", + 65, 32, 65, + GCRY_MD_SHA3_256, GCRY_KEM_RAW_P256R1, + 0, 0 + }, + { + "NIST P-384", + 97, 48, 97, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_P384R1, + 0, 0 + }, + { + "NIST P-521", + 133, 66, 133, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_P521R1, + 0, 0 + }, + { + "brainpoolP256r1", + 65, 32, 65, + GCRY_MD_SHA3_256, GCRY_KEM_RAW_BP256, + 0, 0 + }, + { + "brainpoolP384r1", + 97, 48, 97, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP384, + 0, 0 + }, + { + "brainpoolP512r1", + 129, 64, 129, + GCRY_MD_SHA3_512, GCRY_KEM_RAW_BP512, + 0, 0 + }, + { NULL, 0, 0, 0, 0, 0, 0, 0 } +}; + + +/* Return the ECC parameters for CURVE. CURVE is expected to be the + * canonical name. */ +const struct gnupg_ecc_params * +gnupg_get_ecc_params (const char *curve) +{ + int i; + + for (i = 0; ecc_table[i].curve; i++) + if (!strcmp (ecc_table[i].curve, curve)) + return &ecc_table[i]; + + return NULL; +} diff --git a/common/ksba-io-support.c b/common/ksba-io-support.c index a279b67ad..ff5e49531 100644 --- a/common/ksba-io-support.c +++ b/common/ksba-io-support.c @@ -1,6 +1,6 @@ /* kska-io-support.c - Supporting functions for ksba reader and writer * Copyright (C) 2001-2005, 2007, 2010-2011, 2017 Werner Koch - * Copyright (C) 2006 g10 Code GmbH + * Copyright (C) 2006, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -26,6 +26,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include @@ -96,6 +97,15 @@ struct writer_cb_parm_s char *pem_name; /* Malloced. */ + struct { + gnupg_ksba_progress_cb_t cb; + ctrl_t ctrl; + u32 last_time; /* last time reported */ + uint64_t last; /* last amount reported */ + uint64_t current; /* current amount */ + uint64_t total; /* total amount */ + } progress; + int wrote_begin; int did_finish; @@ -110,6 +120,7 @@ struct writer_cb_parm_s /* Context for this module's functions. */ struct gnupg_ksba_io_s { + int is_writer; /* True if this context refers a writer object. */ union { struct reader_cb_parm_s rparm; struct writer_cb_parm_s wparm; @@ -163,7 +174,7 @@ has_only_base64 (const unsigned char *line, int linelen) { if (*line == '\n' || (linelen > 1 && *line == '\r' && line[1] == '\n')) break; - if ( !strchr (bintoasc, *line) ) + if ( !memchr (bintoasc, *line, sizeof (bintoasc)) ) return 0; } return 1; /* yes */ @@ -527,6 +538,33 @@ simple_reader_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +/* Call the progress callback if its time. We do this very 2 seconds + * or if FORCE is set. However, we also require that at least 64KiB + * have been written to avoid unnecessary progress lines for small + * files. */ +static gpg_error_t +update_write_progress (struct writer_cb_parm_s *parm, size_t count, int force) +{ + gpg_error_t err = 0; + u32 timestamp; + + parm->progress.current += count; + if (parm->progress.current >= (64*1024)) + { + timestamp = make_timestamp (); + if (force || (timestamp - parm->progress.last_time > 1)) + { + parm->progress.last = parm->progress.current; + parm->progress.last_time = timestamp; + err = parm->progress.cb (parm->progress.ctrl, + parm->progress.current, + parm->progress.total); + } + } + return err; +} + + static int base64_writer_cb (void *cb_value, const void *buffer, size_t count) { @@ -535,6 +573,8 @@ base64_writer_cb (void *cb_value, const void *buffer, size_t count) int i, c, idx, quad_count; const unsigned char *p; estream_t stream = parm->stream; + int rc; + size_t nleft; if (!count) return 0; @@ -557,7 +597,7 @@ base64_writer_cb (void *cb_value, const void *buffer, size_t count) for (i=0; i < idx; i++) radbuf[i] = parm->base64.radbuf[i]; - for (p=buffer; count; p++, count--) + for (p=buffer, nleft = count; nleft; p++, nleft--) { radbuf[idx++] = *p; if (idx > 2) @@ -583,7 +623,11 @@ base64_writer_cb (void *cb_value, const void *buffer, size_t count) parm->base64.idx = idx; parm->base64.quad_count = quad_count; - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + /* Note that we use the unencoded count for the progress. */ + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, count, 0); + return rc; } @@ -594,13 +638,16 @@ plain_writer_cb (void *cb_value, const void *buffer, size_t count) { struct writer_cb_parm_s *parm = cb_value; estream_t stream = parm->stream; + int rc; if (!count) return 0; es_write (stream, buffer, count, NULL); - - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, count, 0); + return rc; } @@ -610,6 +657,7 @@ base64_finish_write (struct writer_cb_parm_s *parm) unsigned char *radbuf; int c, idx, quad_count; estream_t stream = parm->stream; + int rc; if (!parm->wrote_begin) return 0; /* Nothing written or we are not called in base-64 mode. */ @@ -656,7 +704,10 @@ base64_finish_write (struct writer_cb_parm_s *parm) es_fputs ("-----\n", stream); } - return es_ferror (stream)? gpg_error_from_syserror () : 0; + rc = es_ferror (stream)? gpg_error_from_syserror () : 0; + if (!rc && parm->progress.cb) + rc = update_write_progress (parm, 0, 1); + return rc; } @@ -788,6 +839,7 @@ gnupg_ksba_create_writer (gnupg_ksba_io_t *ctx, unsigned int flags, *ctx = xtrycalloc (1, sizeof **ctx); if (!*ctx) return gpg_error_from_syserror (); + (*ctx)->is_writer = 1; rc = ksba_writer_new (&w); if (rc) @@ -865,3 +917,37 @@ gnupg_ksba_destroy_writer (gnupg_ksba_io_t ctx) xfree (ctx->u.wparm.pem_name); xfree (ctx); } + + +/* Set a callback to the writer object. CTRL will be bassed to the + * callback. */ +void +gnupg_ksba_set_progress_cb (gnupg_ksba_io_t ctx, + gnupg_ksba_progress_cb_t cb, ctrl_t ctrl) +{ + struct writer_cb_parm_s *parm; + + if (!ctx || !ctx->is_writer) + return; /* Currently only supported for writer objects. */ + parm = &ctx->u.wparm; + + parm->progress.cb = cb; + parm->progress.ctrl = ctrl; + parm->progress.last_time = 0; + parm->progress.last = 0; + parm->progress.current = 0; + parm->progress.total = 0; +} + + +/* Update the total count for the progress thingy. */ +void +gnupg_ksba_set_total (gnupg_ksba_io_t ctx, uint64_t total) +{ + struct writer_cb_parm_s *parm; + + if (!ctx || !ctx->is_writer) + return; /* Currently only supported for writer objects. */ + parm = &ctx->u.wparm; + parm->progress.total = total; +} diff --git a/common/ksba-io-support.h b/common/ksba-io-support.h index 02e541b16..1dbc303c8 100644 --- a/common/ksba-io-support.h +++ b/common/ksba-io-support.h @@ -25,6 +25,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #ifndef GNUPG_KSBA_IO_SUPPORT_H @@ -42,6 +43,10 @@ /* Context object. */ typedef struct gnupg_ksba_io_s *gnupg_ksba_io_t; +/* Progress callback type. */ +typedef gpg_error_t (*gnupg_ksba_progress_cb_t)(ctrl_t ctrl, + uint64_t current, + uint64_t total); gpg_error_t gnupg_ksba_create_reader (gnupg_ksba_io_t *ctx, @@ -57,10 +62,13 @@ gpg_error_t gnupg_ksba_create_writer (gnupg_ksba_io_t *ctx, const char *pem_name, estream_t stream, ksba_writer_t *r_writer); - gpg_error_t gnupg_ksba_finish_writer (gnupg_ksba_io_t ctx); void gnupg_ksba_destroy_writer (gnupg_ksba_io_t ctx); +void gnupg_ksba_set_progress_cb (gnupg_ksba_io_t ctx, + gnupg_ksba_progress_cb_t cb, ctrl_t ctrl); +void gnupg_ksba_set_total (gnupg_ksba_io_t ctx, uint64_t total); + diff --git a/common/mbox-util.c b/common/mbox-util.c index a9086a3f5..fb6d06780 100644 --- a/common/mbox-util.c +++ b/common/mbox-util.c @@ -57,35 +57,6 @@ mem_count_chr (const void *buffer, int c, size_t length) } -/* This is a case-sensitive version of our memistr. I wonder why no - standard function memstr exists but I better do not use the name - memstr to avoid future conflicts. */ -static const char * -my_memstr (const void *buffer, size_t buflen, const char *sub) -{ - const unsigned char *buf = buffer; - const unsigned char *t = (const unsigned char *)buf; - const unsigned char *s = (const unsigned char *)sub; - size_t n = buflen; - - for ( ; n ; t++, n-- ) - { - if (*t == *s) - { - for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) - ; - if (!*s) - return (const char*)buf; - t = (const unsigned char *)buf; - s = (const unsigned char *)sub ; - n = buflen; - } - } - return NULL; -} - - - static int string_has_ctrl_or_space (const char *string) { @@ -159,7 +130,7 @@ is_valid_mailbox_mem (const void *name_arg, size_t namelen) || *name == '@' || name[namelen-1] == '@' || name[namelen-1] == '.' - || my_memstr (name, namelen, "..")); + || gnupg_memstr (name, namelen, "..")); } diff --git a/common/miscellaneous.c b/common/miscellaneous.c index f19cc539d..a41acc240 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -36,27 +36,6 @@ #include "iobuf.h" #include "i18n.h" -/* Used by libgcrypt for logging. */ -static void -my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) -{ - (void)dummy; - - /* Map the log levels. */ - switch (level) - { - case GCRY_LOG_CONT: level = GPGRT_LOGLVL_CONT; break; - case GCRY_LOG_INFO: level = GPGRT_LOGLVL_INFO; break; - case GCRY_LOG_WARN: level = GPGRT_LOGLVL_WARN; break; - case GCRY_LOG_ERROR:level = GPGRT_LOGLVL_ERROR; break; - case GCRY_LOG_FATAL:level = GPGRT_LOGLVL_FATAL; break; - case GCRY_LOG_BUG: level = GPGRT_LOGLVL_BUG; break; - case GCRY_LOG_DEBUG:level = GPGRT_LOGLVL_DEBUG; break; - default: level = GPGRT_LOGLVL_ERROR; break; - } - log_logv (level, fmt, arg_ptr); -} - /* This function is called by libgcrypt on a fatal error. */ static void @@ -100,7 +79,6 @@ my_gcry_outofcore_handler (void *opaque, size_t req_n, unsigned int flags) void setup_libgcrypt_logging (void) { - gcry_set_log_handler (my_gcry_logger, NULL); gcry_set_fatalerror_handler (my_gcry_fatalerror_handler, NULL); gcry_set_outofcore_handler (my_gcry_outofcore_handler, NULL); } @@ -415,112 +393,6 @@ decode_c_string (const char *src) } -/* Check whether (BUF,LEN) is valid header for an OpenPGP compressed - * packet. LEN should be at least 6. */ -static int -is_openpgp_compressed_packet (const unsigned char *buf, size_t len) -{ - int c, ctb, pkttype; - int lenbytes; - - ctb = *buf++; len--; - if (!(ctb & 0x80)) - return 0; /* Invalid packet. */ - - if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */ - { - pkttype = (ctb & 0x3f); - if (!len) - return 0; /* Expected first length octet missing. */ - c = *buf++; len--; - if (c < 192) - ; - else if (c < 224) - { - if (!len) - return 0; /* Expected second length octet missing. */ - } - else if (c == 255) - { - if (len < 4) - return 0; /* Expected length octets missing */ - } - } - else /* Old style CTB. */ - { - pkttype = (ctb>>2)&0xf; - lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); - if (len < lenbytes) - return 0; /* Not enough length bytes. */ - } - - return (pkttype == 8); -} - - - -/* - * Check if the file is compressed. You need to pass the first bytes - * of the file as (BUF,BUFLEN). Returns true if the buffer seems to - * be compressed. - */ -int -is_file_compressed (const byte *buf, unsigned int buflen) -{ - int i; - - struct magic_compress_s - { - byte len; - byte extchk; - byte magic[5]; - } magic[] = - { - { 3, 0, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */ - { 3, 0, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */ - { 4, 0, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */ - { 5, 0, { '%', 'P', 'D', 'F', '-'} }, /* PDF */ - { 4, 1, { 0xff, 0xd8, 0xff, 0xe0 } }, /* Maybe JFIF */ - { 5, 2, { 0x89, 'P','N','G', 0x0d} } /* Likely PNG */ - }; - - if ( buflen < 6 ) - { - return 0; /* Too short to check - assume uncompressed. */ - } - - for ( i = 0; i < DIM (magic); i++ ) - { - if (!memcmp( buf, magic[i].magic, magic[i].len)) - { - switch (magic[i].extchk) - { - case 0: - return 1; /* Is compressed. */ - case 1: - if (buflen > 11 && !memcmp (buf + 6, "JFIF", 5)) - return 1; /* JFIF: this likely a compressed JPEG. */ - break; - case 2: - if (buflen > 8 - && buf[5] == 0x0a && buf[6] == 0x1a && buf[7] == 0x0a) - return 1; /* This is a PNG. */ - break; - default: - break; - } - } - } - - if (buflen >= 6 && is_openpgp_compressed_packet (buf, buflen)) - { - return 1; /* Already compressed. */ - } - - return 0; /* Not detected as compressed. */ -} - - /* Try match against each substring of multistr, delimited by | */ int match_multistr (const char *multistr,const char *match) @@ -793,3 +665,53 @@ parse_compatibility_flags (const char *string, unsigned int *flagvar, *flagvar |= result; return 0; } + + +/* Convert STRING consisting of base64 characters into its binary + * representation and store the result in a newly allocated buffer at + * R_BUFFER with its length at R_BUFLEN. If TITLE is NULL a plain + * base64 decoding is done. If it is the empty string the decoder + * will skip everything until a "-----BEGIN " line has been seen, + * decoding then ends at a "----END " line. On failure the function + * returns an error code and sets R_BUFFER to NULL. If the decoded + * data has a length of 0 a dummy buffer will still be allocated and + * the length is set to 0. */ +gpg_error_t +b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + gpgrt_b64state_t state; + size_t nbytes; + char *buffer; + + *r_buffer = NULL; + *r_buflen = 0; + + buffer = xtrystrdup (string); + if (!buffer) + return gpg_error_from_syserror(); + + state = gpgrt_b64dec_start (title); + if (!state) + { + err = gpg_error_from_syserror (); + xfree (buffer); + return err; + } + err = gpgrt_b64dec_proc (state, buffer, strlen (buffer), &nbytes); + if (!err) + { + err = gpgrt_b64dec_finish (state); + state = NULL; + } + if (err) + xfree (buffer); + else + { + *r_buffer = buffer; + *r_buflen = nbytes; + } + gpgrt_b64dec_finish (state); /* Make sure it is released. */ + return err; +} diff --git a/common/mischelp.c b/common/mischelp.c index ee8500297..ef70c9d83 100644 --- a/common/mischelp.c +++ b/common/mischelp.c @@ -126,80 +126,3 @@ same_file_p (const char *name1, const char *name2) } return yes; } - - -/* - timegm() is a GNU function that might not be available everywhere. - It's basically the inverse of gmtime() - you give it a struct tm, - and get back a time_t. It differs from mktime() in that it handles - the case where the struct tm is UTC and the local environment isn't. - - Note, that this replacement implementation might not be thread-safe! - - Some BSDs don't handle the putenv("foo") case properly, so we use - unsetenv if the platform has it to remove environment variables. -*/ -#ifndef HAVE_TIMEGM -time_t -timegm (struct tm *tm) -{ -#ifdef HAVE_W32_SYSTEM - /* This one is thread safe. */ - SYSTEMTIME st; - FILETIME ft; - unsigned long long cnsecs; - - st.wYear = tm->tm_year + 1900; - st.wMonth = tm->tm_mon + 1; - st.wDay = tm->tm_mday; - st.wHour = tm->tm_hour; - st.wMinute = tm->tm_min; - st.wSecond = tm->tm_sec; - st.wMilliseconds = 0; /* Not available. */ - st.wDayOfWeek = 0; /* Ignored. */ - - /* System time is UTC thus the conversion is pretty easy. */ - if (!SystemTimeToFileTime (&st, &ft)) - { - gpg_err_set_errno (EINVAL); - return (time_t)(-1); - } - - cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) - | ft.dwLowDateTime); - cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ - return (time_t)(cnsecs / 10000000ULL); - -#else /* (Non thread safe implementation!) */ - - time_t answer; - char *zone; - - zone=getenv("TZ"); - putenv("TZ=UTC"); - tzset(); - answer=mktime(tm); - if(zone) - { - static char *old_zone; - - if (!old_zone) - { - old_zone = malloc(3+strlen(zone)+1); - if (old_zone) - { - strcpy(old_zone,"TZ="); - strcat(old_zone,zone); - } - } - if (old_zone) - putenv (old_zone); - } - else - gnupg_unsetenv("TZ"); - - tzset(); - return answer; -#endif -} -#endif /*!HAVE_TIMEGM*/ diff --git a/common/mischelp.h b/common/mischelp.h index bdee5a443..7359ec29e 100644 --- a/common/mischelp.h +++ b/common/mischelp.h @@ -38,12 +38,6 @@ int same_file_p (const char *name1, const char *name2); -#ifndef HAVE_TIMEGM -#include -time_t timegm (struct tm *tm); -#endif /*!HAVE_TIMEGM*/ - - #define DIM(v) (sizeof(v)/sizeof((v)[0])) #define DIMof(type,member) DIM(((type *)0)->member) diff --git a/common/mkdir_p.h b/common/mkdir_p.h index 1e939b32b..08f388916 100644 --- a/common/mkdir_p.h +++ b/common/mkdir_p.h @@ -34,7 +34,7 @@ /* Create a directory as well as any missing parents. - The arguments must be NULL termianted. If DIRECTORY_COMPONENTS... + The arguments must be NULL terminated. If DIRECTORY_COMPONENTS... consists of two elements, "foo/bar" and "xyzzy", this function will first try to create the directory "foo/bar" and then the directory "foo/bar/xyzzy". On success returns 0, otherwise an error code is diff --git a/common/name-value.c b/common/name-value.c index 67429e47f..ea6a84f56 100644 --- a/common/name-value.c +++ b/common/name-value.c @@ -48,6 +48,7 @@ struct name_value_container struct name_value_entry *first; struct name_value_entry *last; unsigned int private_key_mode:1; + unsigned int modified:1; }; @@ -87,11 +88,15 @@ my_error (gpg_err_code_t ec) /* Allocation and deallocation. */ -/* Allocate a private key container structure. */ +/* Allocate a name value container structure. */ nvc_t nvc_new (void) { - return xtrycalloc (1, sizeof (struct name_value_container)); + nvc_t nvc; + nvc = xtrycalloc (1, sizeof (struct name_value_container)); + if (nvc) + nvc->modified = 1; + return nvc; } @@ -142,6 +147,24 @@ nvc_release (nvc_t pk) xfree (pk); } + +/* Return the modified-flag of the container and clear it if CLEAR is + * set. That flag is set for a new container and set with each + * update. */ +int +nvc_modified (nvc_t pk, int clear) +{ + int modified; + + if (!pk) + return 0; + modified = pk->modified; + if (clear) + pk->modified = 0; + return modified; +} + + /* Dealing with names and values. */ @@ -427,6 +450,8 @@ _nvc_add (nvc_t pk, char *name, char *value, strlist_t raw_value, else pk->first = pk->last = e; + pk->modified = 1; + leave: if (err) { @@ -476,27 +501,46 @@ nvc_set (nvc_t pk, const char *name, const char *value) e = nvc_lookup (pk, name); if (e) - { - char *v; - - v = xtrystrdup (value); - if (v == NULL) - return my_error_from_syserror (); - - free_strlist_wipe (e->raw_value); - e->raw_value = NULL; - if (e->value) - wipememory (e->value, strlen (e->value)); - xfree (e->value); - e->value = v; - - return 0; - } + return nve_set (pk, e, value); else return nvc_add (pk, name, value); } +/* Update entry E to VALUE. PK is optional; if given its modified + * flag will be updated. */ +gpg_error_t +nve_set (nvc_t pk, nve_t e, const char *value) +{ + char *v; + + if (!e) + return GPG_ERR_INV_ARG; + + if (e->value && value && !strcmp (e->value, value)) + { + /* Setting same value - ignore this call and don't set the + * modified flag (if PK is given). */ + return 0; + } + + v = xtrystrdup (value? value:""); + if (!v) + return my_error_from_syserror (); + + free_strlist_wipe (e->raw_value); + e->raw_value = NULL; + if (e->value) + wipememory (e->value, strlen (e->value)); + xfree (e->value); + e->value = v; + if (pk) + pk->modified = 1; + + return 0; +} + + /* Delete the given entry from PK. */ void nvc_delete (nvc_t pk, nve_t entry) @@ -512,6 +556,7 @@ nvc_delete (nvc_t pk, nve_t entry) pk->last = entry->prev; nve_release (entry, pk->private_key_mode); + pk->modified = 1; } @@ -592,7 +637,7 @@ nve_next_value (nve_t entry, const char *name) /* Return the string for the first entry in NVC with NAME. If an * entry with NAME is missing in NVC or its value is the empty string - * NULL is returned. Note that the The returned string is a pointer + * NULL is returned. Note that the the returned string is a pointer * into NVC. */ const char * nvc_get_string (nvc_t nvc, const char *name) diff --git a/common/name-value.h b/common/name-value.h index 504b5d0f0..dfded6678 100644 --- a/common/name-value.h +++ b/common/name-value.h @@ -50,6 +50,9 @@ nvc_t nvc_new_private_key (void); /* Release a name value container structure. */ void nvc_release (nvc_t pk); +/* Return the modified flag and optionally clear it. */ +int nvc_modified (nvc_t pk, int clear); + /* Get the name. */ char *nve_name (nve_t pke); @@ -92,6 +95,9 @@ gpg_error_t nvc_add (nvc_t pk, const char *name, const char *value); first entry is updated. */ gpg_error_t nvc_set (nvc_t pk, const char *name, const char *value); +/* Update entry E to VALUE. PK is optional. */ +gpg_error_t nve_set (nvc_t pk, nve_t e, const char *value); + /* Delete the given entry from PK. */ void nvc_delete (nvc_t pk, nve_t pke); diff --git a/common/openpgp-fpr.c b/common/openpgp-fpr.c index 7b110085f..699eee9ee 100644 --- a/common/openpgp-fpr.c +++ b/common/openpgp-fpr.c @@ -136,7 +136,7 @@ compute_openpgp_fpr (int keyversion, int pgpalgo, unsigned long timestamp, /* log_printhex (iov[i].data, iov[i].len, "cmpfpr i=%d: ", i); */ err = gcry_md_hash_buffers (hashalgo, 0, result, iov, iovcnt); - /* log_printhex (result, 20, "fingerpint: "); */ + /* log_printhex (result, 20, "fingerprint: "); */ /* Better clear the first element because it was set by us. */ iov[0].size = 0; @@ -231,7 +231,8 @@ compute_openpgp_fpr_ecc (int keyversion, unsigned long timestamp, unsigned char nbits_q[2]; unsigned int n; - curveoidstr = openpgp_curve_to_oid (curvename, &curvebits, &pgpalgo); + curveoidstr = openpgp_curve_to_oid (curvename, &curvebits, &pgpalgo, + (keyversion > 4)); err = openpgp_oid_from_str (curveoidstr, &curveoid); if (err) goto leave; diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c index 493054950..91081231c 100644 --- a/common/openpgp-oid.c +++ b/common/openpgp-oid.c @@ -43,23 +43,37 @@ static struct { const char *oidstr; /* IETF formatted OID. */ unsigned int nbits; /* Nominal bit length of the curve. */ const char *alias; /* NULL or alternative name of the curve. */ + const char *abbr; /* NULL or abbreviated name of the curve. */ int pubkey_algo; /* Required OpenPGP algo or 0 for ECDSA/ECDH. */ + enum gcry_kem_algos kem_algo; /* 0 or the KEM algorithm for PQC. */ } oidtable[] = { - { "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", PUBKEY_ALGO_ECDH }, - { "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", PUBKEY_ALGO_EDDSA }, - { "Curve25519", "1.3.101.110", 255, "cv25519", PUBKEY_ALGO_ECDH }, - { "Ed25519", "1.3.101.112", 255, "ed25519", PUBKEY_ALGO_EDDSA }, - { "X448", "1.3.101.111", 448, "cv448", PUBKEY_ALGO_ECDH }, - { "Ed448", "1.3.101.113", 456, "ed448", PUBKEY_ALGO_EDDSA }, + { "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", NULL, + PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 /* only during development */}, + { "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", NULL, + PUBKEY_ALGO_EDDSA }, + { "Curve25519", "1.3.101.110", 255, "cv25519", NULL, + PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 }, + { "Ed25519", "1.3.101.112", 255, "ed25519", NULL, + PUBKEY_ALGO_EDDSA }, + { "X448", "1.3.101.111", 448, "cv448", NULL, + PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X448 }, + { "Ed448", "1.3.101.113", 456, "ed448", NULL, + PUBKEY_ALGO_EDDSA }, - { "NIST P-256", "1.2.840.10045.3.1.7", 256, "nistp256" }, - { "NIST P-384", "1.3.132.0.34", 384, "nistp384" }, - { "NIST P-521", "1.3.132.0.35", 521, "nistp521" }, + { "NIST P-256", "1.2.840.10045.3.1.7", 256, "nistp256", NULL, + 0, GCRY_KEM_RAW_P256R1 }, + { "NIST P-384", "1.3.132.0.34", 384, "nistp384", NULL, + 0, GCRY_KEM_RAW_P384R1 }, + { "NIST P-521", "1.3.132.0.35", 521, "nistp521", NULL, + 0, GCRY_KEM_RAW_P521R1 }, - { "brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 256 }, - { "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384 }, - { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512 }, + { "brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 256, NULL, "bp256", + 0, GCRY_KEM_RAW_BP256 }, + { "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384, NULL, "bp384", + 0, GCRY_KEM_RAW_BP384 }, + { "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512, NULL, "bp512", + 0, GCRY_KEM_RAW_BP512 }, { "secp256k1", "1.3.132.0.10", 256 }, @@ -118,7 +132,7 @@ make_flagged_int (unsigned long value, char *buf, size_t buflen) /* fixme: figure out the number of bits in an ulong and start with that value as shift (after making it a multiple of 7) a more - straigtforward implementation is to do it in reverse order using + straightforward implementation is to do it in reverse order using a temporary buffer - saves a lot of compares */ for (more=0, shift=28; shift > 0; shift -= 7) { @@ -432,9 +446,11 @@ openpgp_oid_is_cv448 (gcry_mpi_t a) curve names. If R_ALGO is not NULL and a specific ECC algorithm is required for this curve its OpenPGP algorithm number is stored there; otherwise 0 is stored which indicates that ECDSA or ECDH can - be used. */ + be used. SELECTOR specifies which OID should be returned: -1 for + don't care, 0 for old OID, 1 for new OID. */ const char * -openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo) +openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo, + int selector) { int i; unsigned int nbits = 0; @@ -468,6 +484,14 @@ openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo) } } + /* Special handling for Curve25519, where we have two valid OIDs. */ + if (algo && i == 0) + { + /* Select new OID, if wanted. */ + if (selector > 0) + oidstr = oidtable[2].oidstr; + } + if (r_nbits) *r_nbits = nbits; if (r_algo) @@ -477,10 +501,20 @@ openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo) /* Map an OpenPGP OID to the Libgcrypt curve name. Returns NULL for - * unknown curve names. Unless CANON is set we prefer an alias name - * here which is more suitable for printing. */ + * unknown curve names. MODE defines which version of the curve name + * is returned. For example: + * + * | OID | mode=0 | mode=1 | mode=2 | + * |----------------------+-----------------+-----------------+----------| + * | 1.2.840.10045.3.1.7 | nistp256 | NIST P-256 | nistp256 | + * | 1.3.36.3.3.2.8.1.1.7 | brainpoolP256r1 | brainpoolP256r1 | bp256 | + * + * Thus mode 0 returns the name as commonly used gpg, mode 1 returns + * the canonical name, and mode 2 prefers an abbreviated name over the + * commonly used name. + */ const char * -openpgp_oid_to_curve (const char *oidstr, int canon) +openpgp_oid_to_curve (const char *oidstr, int mode) { int i; @@ -489,7 +523,15 @@ openpgp_oid_to_curve (const char *oidstr, int canon) for (i=0; oidtable[i].name; i++) if (!strcmp (oidtable[i].oidstr, oidstr)) - return !canon && oidtable[i].alias? oidtable[i].alias : oidtable[i].name; + { + if (mode == 2) + { + if (oidtable[i].abbr) + return oidtable[i].abbr; + mode = 0; /* No abbreviation - fallback to mode 0. */ + } + return !mode && oidtable[i].alias? oidtable[i].alias : oidtable[i].name; + } return NULL; } @@ -517,6 +559,29 @@ openpgp_oid_or_name_to_curve (const char *oidname, int canon) } +/* Return the KEM algorithm id for the curve with OIDNAME. */ +enum gcry_kem_algos +openpgp_oid_to_kem_algo (const char *oidname) +{ + int i; + + if (!oidname) + return 0; + + for (i=0; oidtable[i].name; i++) + if (!strcmp (oidtable[i].oidstr, oidname)) + return oidtable[i].kem_algo; + + for (i=0; oidtable[i].name; i++) + if (!ascii_strcasecmp (oidtable[i].name, oidname) + || (oidtable[i].alias + && !ascii_strcasecmp (oidtable[i].alias, oidname))) + return oidtable[i].kem_algo; + + return 0; +} + + /* Return true if the curve with NAME is supported. */ static int curve_supported_p (const char *name) @@ -574,7 +639,9 @@ openpgp_is_curve_supported (const char *name, int *r_algo, { if ((!ascii_strcasecmp (name, oidtable[idx].name) || (oidtable[idx].alias - && !ascii_strcasecmp (name, (oidtable[idx].alias)))) + && !ascii_strcasecmp (name, (oidtable[idx].alias))) + || (oidtable[idx].abbr + && !ascii_strcasecmp (name, (oidtable[idx].abbr)))) && curve_supported_p (oidtable[idx].name)) { if (r_algo) @@ -598,6 +665,7 @@ map_gcry_pk_to_openpgp (enum gcry_pk_algos algo) case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA; case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA; case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH; + case GCRY_PK_KEM: return PUBKEY_ALGO_KYBER; default: return algo < 110 ? (pubkey_algo_t)algo : 0; } } diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index 625747983..b3e4fbd5e 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -122,6 +122,9 @@ typedef enum SIGSUBPKT_ATTST_SIGS = 37, /* Attested Certifications. */ SIGSUBPKT_KEY_BLOCK = 38, /* Entire key used. */ + SIGSUBPKT_META_HASH = 40, /* Literal Data Meta Hash. */ + SIGSUBPKT_TRUST_ALIAS = 41, /* Trust Alias. */ + SIGSUBPKT_FLAG_CRITICAL = 128 } sigsubpkttype_t; @@ -162,13 +165,18 @@ typedef enum PUBKEY_ALGO_RSA = 1, PUBKEY_ALGO_RSA_E = 2, /* RSA encrypt only (legacy). */ PUBKEY_ALGO_RSA_S = 3, /* RSA sign only (legacy). */ + PUBKEY_ALGO_KYBER = 8, /* Kyber (FIPS-203 final) */ PUBKEY_ALGO_ELGAMAL_E = 16, /* Elgamal encrypt only. */ PUBKEY_ALGO_DSA = 17, PUBKEY_ALGO_ECDH = 18, /* RFC-6637 */ PUBKEY_ALGO_ECDSA = 19, /* RFC-6637 */ PUBKEY_ALGO_ELGAMAL = 20, /* Elgamal encrypt+sign (legacy). */ /* 21 reserved by OpenPGP. */ - PUBKEY_ALGO_EDDSA = 22, /* EdDSA (not yet assigned). */ + PUBKEY_ALGO_EDDSA = 22, /* EdDSA. */ + /* 29 (was fips203.ipd.2023-08-24 in 1.5.0) */ + PUBKEY_ALGO_DIL3_25519 = 35, /* Dilithium3 + Ed25519 (aka ML-DSA-65) */ + PUBKEY_ALGO_DIL5_448 = 36, /* Dilithium5 + Ed448 (aka ML-DSA-87) */ + PUBKEY_ALGO_SPHINX_SHA2 = 41, /* SPHINX+-simple-SHA2 (aka SLH-DSA-SHA2) */ PUBKEY_ALGO_PRIVATE10 = 110 } pubkey_algo_t; @@ -203,7 +211,7 @@ compress_algo_t; #define OPENPGP_MAX_NPKEY 5 /* Maximum number of public key parameters. */ #define OPENPGP_MAX_NSKEY 7 /* Maximum number of secret key parameters. */ #define OPENPGP_MAX_NSIG 2 /* Maximum number of signature parameters. */ -#define OPENPGP_MAX_NENC 2 /* Maximum number of encryption parameters. */ +#define OPENPGP_MAX_NENC 4 /* Maximum number of encryption parameters. */ /* Decode an rfc4880 encoded S2K count. */ diff --git a/common/recsel.c b/common/recsel.c index ea0858c84..a778419db 100644 --- a/common/recsel.c +++ b/common/recsel.c @@ -64,12 +64,17 @@ struct recsel_expr_s unsigned int not:1; /* Negate operators. */ unsigned int disjun:1;/* Start of a disjunction. */ unsigned int xcase:1; /* String match is case sensitive. */ + unsigned int lefta:1; /* String match is left anchored. */ const char *value; /* (Points into NAME.) */ long numvalue; /* strtol of VALUE. */ char name[1]; /* Name of the property. */ }; +/* Global debug variable. */ +static int recsel_debug; + + /* Helper */ static inline gpg_error_t my_error_from_syserror (void) @@ -85,37 +90,6 @@ my_error (gpg_err_code_t ec) } -/* This is a case-sensitive version of our memistr. I wonder why no - * standard function memstr exists but I better do not use the name - * memstr to avoid future conflicts. - * - * FIXME: Move this to a stringhelp.c - */ -static const char * -my_memstr (const void *buffer, size_t buflen, const char *sub) -{ - const unsigned char *buf = buffer; - const unsigned char *t = (const unsigned char *)buf; - const unsigned char *s = (const unsigned char *)sub; - size_t n = buflen; - - for ( ; n ; t++, n-- ) - { - if (*t == *s) - { - for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) - ; - if (!*s) - return (const char*)buf; - t = (const unsigned char *)buf; - s = (const unsigned char *)sub ; - n = buflen; - } - } - return NULL; -} - - /* Return a pointer to the next logical connection operator or NULL if * none. */ static char * @@ -171,6 +145,7 @@ find_next_lc (char *string) * Values for must be space separated and any of: * * -- VALUE spans to the end of the expression. + * -^ The substring match is left anchored. * -c The string match in this part is done case-sensitive. * -t Do not trim leading and trailing spaces from VALUE. * Note that a space after is here required. @@ -207,6 +182,7 @@ recsel_parse_expr (recsel_expr_t *selector, const char *expression) int toend = 0; int xcase = 0; int notrim = 0; + int lefta = 0; int disjun = 0; char *next_lc = NULL; @@ -237,6 +213,7 @@ recsel_parse_expr (recsel_expr_t *selector, const char *expression) case '-': toend = 1; break; case 'c': xcase = 1; break; case 't': notrim = 1; break; + case '^': lefta = 1; break; default: log_error ("invalid flag '-%c' in expression\n", *expr); recsel_release (se_head); @@ -266,6 +243,7 @@ recsel_parse_expr (recsel_expr_t *selector, const char *expression) se->not = 0; se->disjun = disjun; se->xcase = xcase; + se->lefta = lefta; if (!se_head) se_head = se; @@ -486,6 +464,15 @@ recsel_release (recsel_expr_t a) } +int +recsel_set_debug (int value) +{ + int old = recsel_debug; + recsel_debug = value; + return old; +} + + void recsel_dump (recsel_expr_t selector) { @@ -494,9 +481,10 @@ recsel_dump (recsel_expr_t selector) log_debug ("--- Begin selectors ---\n"); for (se = selector; se; se = se->next) { - log_debug ("%s %s %s %s '%s'\n", + log_debug ("%s %s %s %s %s '%s'\n", se==selector? " ": (se->disjun? "||":"&&"), se->xcase? "-c":" ", + se->lefta? "-^":" ", se->name, se->op == SELECT_SAME? (se->not? "<>":"= "): se->op == SELECT_SUB? (se->not? "!~":"=~"): @@ -536,8 +524,14 @@ recsel_select (recsel_expr_t selector, while (se) { value = getval? getval (cookie, se->name) : NULL; + if (recsel_debug) + log_debug ("%s: name=%s got value '%s'\n", __func__, se->name, value); if (!value) - value = ""; + { + se = se->next; + result = 0; + continue; + } if (!*value) { @@ -560,9 +554,15 @@ recsel_select (recsel_expr_t selector, break; case SELECT_SUB: if (se->xcase) - result = !!my_memstr (value, valuelen, se->value); + result = (gnupg_memstr (value, valuelen, se->value) + && (!se->lefta + || (selen <= valuelen + && !memcmp (value, se->value, selen)))); else - result = !!memistr (value, valuelen, se->value); + result = (memistr (value, valuelen, se->value) + && (!se->lefta + || (selen <= valuelen + && !memicmp (value, se->value, selen)))); break; case SELECT_NONEMPTY: result = !!valuelen; @@ -635,5 +635,7 @@ recsel_select (recsel_expr_t selector, } } + if (recsel_debug) + log_debug ("%s: result=%d\n", __func__, result); return result; } diff --git a/common/recsel.h b/common/recsel.h index 0e0a7928a..a33a0da01 100644 --- a/common/recsel.h +++ b/common/recsel.h @@ -34,6 +34,7 @@ typedef struct recsel_expr_s *recsel_expr_t; gpg_error_t recsel_parse_expr (recsel_expr_t *selector, const char *expr); void recsel_release (recsel_expr_t a); +int recsel_set_debug (int value); void recsel_dump (recsel_expr_t selector); int recsel_select (recsel_expr_t selector, const char *(*getval)(void *cookie, const char *propname), diff --git a/common/session-env.c b/common/session-env.c index f07d9d101..7006201d7 100644 --- a/common/session-env.c +++ b/common/session-env.c @@ -63,6 +63,7 @@ static struct { const char *name; const char *assname; /* Name used by Assuan or NULL. */ + unsigned int disabled;/* The entry is not valid */ } stdenvnames[] = { { "GPG_TTY", "ttyname" }, /* GnuPG specific envvar. */ { "TERM", "ttytype" }, /* Used to set ttytype. */ @@ -84,9 +85,10 @@ static struct modules (eg "xim"). */ { "INSIDE_EMACS" }, /* Set by Emacs before running a process. */ - { "PINENTRY_USER_DATA", "pinentry-user-data"} + { "PINENTRY_USER_DATA", "pinentry-user-data"}, /* Used for communication with non-standard Pinentries. */ + { "PINENTRY_GEOM_HINT" } /* Used to pass window information. */ }; @@ -96,11 +98,41 @@ static struct 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 INITIAL_ARRAYSIZE 14 /* Let's use the number of stdenvnames. */ +#define CHUNK_ARRAYSIZE 16 #define MAXDEFAULT_ARRAYSIZE (INITIAL_ARRAYSIZE + CHUNK_ARRAYSIZE * 5) +/* Modify the list of environment names which are known to gpg-agent. + * This function must be called before the session names are used and + * should not be changed later. The syntax for NAME is: + * + * -FOO := Remove the environment variable FOO from the list + * [+]FOO := Add the environment variable FOO to the list + * [+]FOO:bar := Ditto, but also add "bar" as Assuan alias. + * + * Note that adding environment variables is not yet supported and + * silently ignored. + */ +void +session_env_mod_stdenvnames (const char *name) +{ + int idx; + + if (*name != '-') + return; + name++; + if (!*name) + return; + + for (idx = 0; idx < DIM (stdenvnames); idx++) + { + if (!strcmp (stdenvnames[idx].name, name)) + stdenvnames[idx].disabled = 1; + } +} + + /* 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 @@ -132,6 +164,8 @@ session_env_list_stdenvnames (int *iterator, const char **r_assname) p = commastring; for (idx = 0; idx < DIM (stdenvnames); idx++) { + if (stdenvnames[idx].disabled) + continue; if (idx) *p++ = ','; p = stpcpy (p, stdenvnames[idx].name); @@ -141,10 +175,14 @@ session_env_list_stdenvnames (int *iterator, const char **r_assname) return commastring; } - idx = *iterator; - if (idx < 0 || idx >= DIM (stdenvnames)) - return NULL; - *iterator = idx + 1; + do + { + idx = *iterator; + if (idx < 0 || idx >= DIM (stdenvnames)) + return NULL; + *iterator = idx + 1; + } + while (stdenvnames[idx].disabled); if (r_assname) *r_assname = stdenvnames[idx].assname; return stdenvnames[idx].name; @@ -314,7 +352,7 @@ session_env_putenv (session_env_t se, const char *string) } -/* Same as session_env_putenv but with name and value given as distict +/* Same as session_env_putenv but with name and value given as distinct values. */ gpg_error_t session_env_setenv (session_env_t se, const char *name, const char *value) @@ -354,7 +392,7 @@ session_env_getenv (session_env_t se, const char *name) 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 + tries to return the value through 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. */ diff --git a/common/session-env.h b/common/session-env.h index 8709e223c..c5ceccbd0 100644 --- a/common/session-env.h +++ b/common/session-env.h @@ -33,6 +33,7 @@ struct session_environment_s; typedef struct session_environment_s *session_env_t; +void session_env_mod_stdenvnames (const char *name); const char *session_env_list_stdenvnames (int *iterator, const char **r_assname); diff --git a/common/sexp-parse.h b/common/sexp-parse.h index 0403d65f5..86372e028 100644 --- a/common/sexp-parse.h +++ b/common/sexp-parse.h @@ -104,7 +104,7 @@ smatch (unsigned char const **buf, size_t buflen, const char *token) return 1; } -/* Format VALUE for use as the length indicatior of an S-expression. +/* Format VALUE for use as the length indicator of an S-expression. The caller needs to provide a buffer HELP_BUFFER with a length of HELP_BUFLEN. The return value is a pointer into HELP_BUFFER with the formatted length string. The colon and a trailing nul are diff --git a/common/sexputil.c b/common/sexputil.c index c7471be85..fcd15ebc6 100644 --- a/common/sexputil.c +++ b/common/sexputil.c @@ -199,7 +199,7 @@ make_canon_sexp_pad (gcry_sexp_t sexp, int secure, } /* Return the so called "keygrip" which is the SHA-1 hash of the - public key parameters expressed in a way dependend on the algorithm. + public key parameters expressed in a way dependent on the algorithm. KEY is expected to be an canonical encoded S-expression with a public or private key. KEYLEN is the length of that buffer. @@ -784,11 +784,11 @@ uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata, return err; if (!tok) return gpg_error (GPG_ERR_BAD_PUBKEY); - else if (toklen == 10 || !memcmp ("public-key", tok, toklen)) + else if (toklen == 10 && !memcmp ("public-key", tok, toklen)) ; - else if (toklen == 11 || !memcmp ("private-key", tok, toklen)) + else if (toklen == 11 && !memcmp ("private-key", tok, toklen)) ; - else if (toklen == 20 || !memcmp ("shadowed-private-key", tok, toklen)) + else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen)) ; else return gpg_error (GPG_ERR_BAD_PUBKEY); @@ -992,7 +992,7 @@ get_pk_algo_from_key (gcry_sexp_t key) gcry_sexp_t list; const char *s; size_t n; - char algoname[6]; + char algoname[10]; int algo = 0; list = gcry_sexp_nth (key, 1); @@ -1104,8 +1104,7 @@ pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid) else if (prefix) { const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL); - const char *name = openpgp_oid_to_curve - (openpgp_curve_to_oid (curve, NULL, NULL), 0); + const char *name = openpgp_oid_or_name_to_curve (curve, 0); if (name) result = xtrystrdup (name); @@ -1194,3 +1193,47 @@ cipher_mode_to_string (int mode) default: return "[?]"; } } + +/* Return the canonical name of the ECC curve in KEY. */ +const char * +get_ecc_curve_from_key (gcry_sexp_t key) +{ + gcry_sexp_t list = NULL; + gcry_sexp_t l2 = NULL; + const char *curve_name = NULL; + char *name = NULL; + + /* Check that the first element is valid. */ + 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); + if (!list) + goto leave; + + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + l2 = NULL; + + name = gcry_sexp_nth_string (list, 0); + if (!name) + goto leave; + + if (gcry_pk_map_name (name) != GCRY_PK_ECC) + goto leave; + + l2 = gcry_sexp_find_token (list, "curve", 0); + xfree (name); + name = gcry_sexp_nth_string (l2, 1); + curve_name = openpgp_oid_or_name_to_curve (name, 1); + gcry_sexp_release (l2); + + leave: + xfree (name); + gcry_sexp_release (list); + return curve_name; +} diff --git a/common/signal.c b/common/signal.c index d308c175c..a56e6d6f4 100644 --- a/common/signal.c +++ b/common/signal.c @@ -89,8 +89,12 @@ get_signal_name( int signum ) reentrant. */ #if HAVE_SIGDESCR_NP return sigdescr_np (signum); -#elif HAVE_DECL_SYS_SIGLIST && defined(NSIG) - return (signum >= 0 && signum < NSIG) ? sys_siglist[signum] : "?"; +#elif (HAVE_DECL_SYS_SIGLIST || HAVE_DECL__SYS_SIGLIST) && defined(NSIG) +#if HAVE_DECL_SYS_SIGLIST +#undef _sys_siglist +#define _sys_siglist sys_siglist +#endif + return (signum >= 0 && signum < NSIG) ? _sys_siglist[signum] : "?"; #else return NULL; #endif diff --git a/common/ssh-utils.c b/common/ssh-utils.c index ab29558cf..d27e2e200 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -259,7 +259,7 @@ get_fingerprint (gcry_sexp_t key, int algo, } else { - struct b64state b64s; + gpgrt_b64state_t b64s; estream_t stream; char *p; long int len; @@ -273,15 +273,15 @@ get_fingerprint (gcry_sexp_t key, int algo, goto leave; } - err = b64enc_start_es (&b64s, stream, ""); - if (err) + b64s = gpgrt_b64enc_start (stream, ""); + if (!b64s) { es_fclose (stream); goto leave; } - err = b64enc_write (&b64s, - gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo)); + err = gpgrt_b64enc_write (b64s, gcry_md_read (md, algo), + gcry_md_get_algo_dlen (algo)); if (err) { es_fclose (stream); @@ -289,7 +289,7 @@ get_fingerprint (gcry_sexp_t key, int algo, } /* Finish, get the length, and close the stream. */ - err = b64enc_finish (&b64s); + err = gpgrt_b64enc_finish (b64s); len = es_ftell (stream); es_fclose (stream); if (err) @@ -566,7 +566,7 @@ ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, const char *identifier = NULL; void *blob = NULL; size_t bloblen; - struct b64state b64_state; + gpgrt_b64state_t b64_state; algo = get_pk_algo_from_key (key); if (algo == 0) @@ -624,15 +624,15 @@ ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, es_fprintf (stream, "%s ", identifier); - err = b64enc_start_es (&b64_state, stream, ""); - if (err) + b64_state = gpgrt_b64enc_start (stream, ""); + if (!b64_state) { es_free (blob); - return err; + return gpg_error_from_syserror (); } - err = b64enc_write (&b64_state, blob, bloblen); - b64enc_finish (&b64_state); + err = gpgrt_b64enc_write (b64_state, blob, bloblen); + gpgrt_b64enc_finish (b64_state); es_free (blob); if (err) return err; diff --git a/common/status.h b/common/status.h index e4cf23ee1..0a1266d3c 100644 --- a/common/status.h +++ b/common/status.h @@ -54,6 +54,7 @@ enum STATUS_NEED_PASSPHRASE, STATUS_VALIDSIG, STATUS_ASSERT_SIGNER, + STATUS_ASSERT_PUBKEY_ALGO, STATUS_SIG_ID, STATUS_ENC_TO, STATUS_NODATA, @@ -152,6 +153,7 @@ enum STATUS_TRUNCATED, STATUS_MOUNTPOINT, STATUS_BLOCKDEV, + STATUS_PLAINDEV, /* The decrypted virtual device. */ STATUS_PINENTRY_LAUNCHED, diff --git a/common/stringhelp.c b/common/stringhelp.c index 6959299e4..9a2265258 100644 --- a/common/stringhelp.c +++ b/common/stringhelp.c @@ -161,6 +161,35 @@ ascii_memistr ( const void *buffer, size_t buflen, const char *sub ) } +/* This is a case-sensitive version of our memistr. I wonder why no + * standard function memstr exists but we better do not use the name + * memstr to avoid future conflicts. + */ +const char * +gnupg_memstr (const void *buffer, size_t buflen, const char *sub) +{ + const unsigned char *buf = buffer; + const unsigned char *t = (const unsigned char *)buf; + const unsigned char *s = (const unsigned char *)sub; + size_t n = buflen; + + for ( ; n ; t++, n-- ) + { + if (*t == *s) + { + for (buf = t++, buflen = n--, s++; n && *t ==*s; t++, s++, n--) + ; + if (!*s) + return (const char*)buf; + t = (const unsigned char *)buf; + s = (const unsigned char *)sub ; + n = buflen; + } + } + return NULL; +} + + /* This function is similar to strncpy(). However it won't copy more * than N - 1 characters and makes sure that a '\0' is appended. With * N given as 0, nothing will happen. With DEST given as NULL, memory @@ -696,7 +725,7 @@ compare_filenames (const char *a, const char *b) /* Convert a base-10 number in STRING into a 64 bit unsigned int * value. Leading white spaces are skipped but no error checking is - * done. Thus it is similar to atoi(). */ + * done. Thus it is similar to atoi(). See also scan_secondsstr. */ uint64_t string_to_u64 (const char *string) { @@ -1689,10 +1718,16 @@ format_text (const char *text_in, int target_cols, int max_cols) } -/* Substitute environment variables in STRING and return a new string. - * On error the function returns NULL. */ +/* Substitute variables in STRING and return a new string. GETVAL is + * a function which maps NAME to its value; that value is a string + * which may not change during the execution time of this function. + * If GETVAL returns NULL substitute_vars returns NULL and the caller + * may inspect ERRNO for the reason. In all other error cases this + * function also returns NULL. Caller must free the returned string. */ char * -substitute_envvars (const char *string) +substitute_vars (const char *string, + const char *(*getval)(void *cookie, const char *name), + void *cookie) { char *line, *p, *pend; const char *value; @@ -1743,19 +1778,22 @@ substitute_envvars (const char *string) { int save = *pend; *pend = 0; - value = getenv (p+2); + value = getval (cookie, p+2); *pend++ = save; } else { int save = *pend; *pend = 0; - value = getenv (p+1); + value = getval (cookie, p+1); *pend = save; } if (!value) - value = ""; + { + xfree (result); + return NULL; + } valuelen = strlen (value); if (valuelen <= pend - p) { @@ -1791,3 +1829,26 @@ substitute_envvars (const char *string) leave: return result; } + + +/* Helper for substitute_envvars. */ +static const char * +subst_getenv (void *cookie, const char *name) +{ + const char *s; + + (void)cookie; + + s = getenv (name); + return s? s : ""; +} + + +/* Substitute environment variables in STRING and return a new string. + * On error the function returns NULL. */ +char * +substitute_envvars (const char *string) +{ + return substitute_vars (string, subst_getenv, NULL); + +} diff --git a/common/stringhelp.h b/common/stringhelp.h index 915b3aa72..d93373ec5 100644 --- a/common/stringhelp.h +++ b/common/stringhelp.h @@ -40,6 +40,7 @@ char *has_leading_keyword (const char *string, const char *keyword); const char *memistr (const void *buf, size_t buflen, const char *sub); +const char *gnupg_memstr (const void *buffer, size_t buflen, const char *sub); char *mem2str( char *, const void *, size_t); char *trim_spaces( char *string ); char *ascii_trim_spaces (char *string); @@ -169,7 +170,10 @@ int compare_version_strings (const char *my_version, const char *req_version); /* Format a string so that it fits within about TARGET_COLS columns. */ char *format_text (const char *text, int target_cols, int max_cols); -/* Substitute environmen variabales in STRING. */ +/* Substitute variables in STRING. */ +char *substitute_vars (const char *string, + const char *(*getval)(void *cookie, const char *name), + void *cookie); char *substitute_envvars (const char *string); diff --git a/common/strlist.c b/common/strlist.c index 6feb3a45a..4ad0b0816 100644 --- a/common/strlist.c +++ b/common/strlist.c @@ -1,6 +1,6 @@ /* strlist.c - string helpers * Copyright (C) 1998, 2000, 2001, 2006 Free Software Foundation, Inc. - * Copyright (C) 2015 g10 Code GmbH + * Copyright (C) 2015, 2024 g10 Code GmbH * * This file is part of GnuPG. * @@ -27,6 +27,7 @@ * 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 . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) */ #include @@ -134,27 +135,39 @@ append_to_strlist( strlist_t *list, const char *string ) } +/* Core of append_to_strlist_try which take the length of the string. + * Return the item added to the end of the list. Or NULL in case of + * an error. */ +static strlist_t +do_append_to_strlist (strlist_t *list, const char *string, size_t stringlen) +{ + strlist_t r, sl; + + sl = xtrymalloc (sizeof *sl + stringlen); + if (!sl) + return NULL; + + sl->flags = 0; + memcpy (sl->d, string, stringlen); + sl->d[stringlen] = 0; + sl->next = NULL; + if (!*list) + *list = sl; + else + { + for (r = *list; r->next; r = r->next) + ; + r->next = sl; + } + return sl; +} + + /* Add STRING to the LIST at the end. */ strlist_t append_to_strlist_try (strlist_t *list, const char *string) { - strlist_t r, sl; - - sl = xtrymalloc( sizeof *sl + strlen(string)); - if (sl == NULL) - return NULL; - - sl->flags = 0; - strcpy(sl->d, string); - sl->next = NULL; - if( !*list ) - *list = sl; - else { - for( r = *list; r->next; r = r->next ) - ; - r->next = sl; - } - return sl; + return do_append_to_strlist (list, string, strlen (string)); } @@ -175,6 +188,75 @@ append_to_strlist2( strlist_t *list, const char *string, int is_utf8 ) } +/* Tokenize STRING using the delimiters from DELIM and append each + * token to the string list LIST. On success a pinter into LIST with + * the first new token is returned. Returns NULL on error and sets + * ERRNO. Take care, an error with ENOENT set mean that no tokens + * were found in STRING. */ +strlist_t +tokenize_to_strlist (strlist_t *list, const char *string, const char *delim) +{ + const char *s, *se; + size_t n; + strlist_t newlist = NULL; + strlist_t tail; + + s = string; + do + { + se = strpbrk (s, delim); + if (se) + n = se - s; + else + n = strlen (s); + if (!n) + continue; /* Skip empty string. */ + tail = do_append_to_strlist (&newlist, s, n); + if (!tail) + { + free_strlist (newlist); + return NULL; + } + trim_spaces (tail->d); + if (!*tail->d) /* Remove new but empty item from the list. */ + { + tail = strlist_prev (newlist, tail); + if (tail) + { + free_strlist (tail->next); + tail->next = NULL; + } + else if (newlist) + { + free_strlist (newlist); + newlist = NULL; + } + continue; + } + } + while (se && (s = se + 1)); + + if (!newlist) + { + /* Not items found. Indicate this by returnning NULL with errno + * set to ENOENT. */ + gpg_err_set_errno (ENOENT); + return NULL; + } + + /* Append NEWLIST to LIST. */ + if (!*list) + *list = newlist; + else + { + for (tail = *list; tail->next; tail = tail->next) + ; + tail->next = newlist; + } + return newlist; +} + + /* Return a copy of LIST. This function terminates the process on memory shortage.*/ strlist_t diff --git a/common/strlist.h b/common/strlist.h index 641ea06cb..bf1ffa903 100644 --- a/common/strlist.h +++ b/common/strlist.h @@ -52,6 +52,9 @@ strlist_t append_to_strlist_try (strlist_t *list, const char *string); strlist_t append_to_strlist2 (strlist_t *list, const char *string, int is_utf8); +strlist_t tokenize_to_strlist (strlist_t *list, + const char *string, const char *delim); + strlist_t strlist_copy (strlist_t list); strlist_t strlist_prev (strlist_t head, strlist_t node); diff --git a/common/sysutils.c b/common/sysutils.c index 01510ddb0..2bacae2ea 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -113,6 +113,8 @@ static int allow_special_filenames; #ifdef HAVE_W32_SYSTEM /* State of gnupg_inhibit_set_foregound_window. */ static int inhibit_set_foregound_window; +/* Disable the use of _open_osfhandle. */ +static int no_translate_sys2libc_fd; #endif @@ -252,6 +254,9 @@ map_w32_to_errno (DWORD w32_err) case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_FILE_INVALID: + return EIO; + /* This mapping has been taken from reactOS. */ case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; case ERROR_ARENA_TRASHED: return ENOMEM; @@ -324,15 +329,17 @@ map_w32_to_errno (DWORD w32_err) #endif /*HAVE_W32_SYSTEM*/ -/* Set ERRNO from the Windows error. EC may be -1 to use the last error. */ +/* Set ERRNO from the Windows error. EC may be -1 to use the last + * error. Returns the Windows error code. */ #ifdef HAVE_W32_SYSTEM -void +int gnupg_w32_set_errno (int ec) { /* FIXME: Replace by gpgrt_w32_set_errno. */ if (ec == -1) ec = GetLastError (); _set_errno (map_w32_to_errno (ec)); + return ec; } #endif /*HAVE_W32_SYSTEM*/ @@ -346,6 +353,16 @@ enable_special_filenames (void) } +/* Disable the use use of _open_osfhandle on Windows. */ +void +disable_translate_sys2libc_fd (void) +{ +#ifdef HAVE_W32_SYSTEM + no_translate_sys2libc_fd = 1; +#endif +} + + /* Return a string which is used as a kind of process ID. */ const byte * get_session_marker (size_t *rlen) @@ -532,10 +549,10 @@ gnupg_usleep (unsigned int usecs) different from the libc file descriptors (like open). This function translates system file handles to libc file handles. FOR_WRITE gives the direction of the handle. */ -int +#if defined(HAVE_W32_SYSTEM) +static int translate_sys2libc_fd (gnupg_fd_t fd, int for_write) { -#if defined(HAVE_W32_SYSTEM) int x; if (fd == GNUPG_INVALID_FD) @@ -547,27 +564,87 @@ translate_sys2libc_fd (gnupg_fd_t fd, int for_write) if (x == -1) log_error ("failed to translate osfhandle %p\n", (void *) fd); return x; -#else /*!HAVE_W32_SYSTEM */ +} +#endif /*!HAVE_W32_SYSTEM */ + + +/* This is the same as translate_sys2libc_fd but takes an integer + which is assumed to be such an system handle. */ +int +translate_sys2libc_fd_int (int fd, int for_write) +{ +#ifdef HAVE_W32_SYSTEM + if (fd <= 2 || no_translate_sys2libc_fd) + return fd; /* Do not do this for stdin, stdout, and stderr. */ + + return translate_sys2libc_fd ((void*)(intptr_t)fd, for_write); +#else (void)for_write; return fd; #endif } -/* This is the same as translate_sys2libc_fd but takes an integer - which is assumed to be such an system handle. On WindowsCE the - passed FD is a rendezvous ID and the function finishes the pipe - creation. */ -int -translate_sys2libc_fd_int (int fd, int for_write) -{ -#ifdef HAVE_W32_SYSTEM - if (fd <= 2) - return fd; /* Do not do this for error, stdin, stdout, stderr. */ - return translate_sys2libc_fd ((void*)fd, for_write); +/* + * Parse the string representation of a file reference (file handle on + * Windows or file descriptor on POSIX) in FDSTR. The string + * representation may be either of following: + + * (1) 0, 1, or 2 which means stdin, stdout, and stderr, respectively. + * (2) Integer representation (by %d of printf). + * (3) Hex representation which starts as "0x". + * + * Then, fill R_SYSHD, according to the value of a file reference. + * + */ +gpg_error_t +gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd) +{ + int fd = -1; +#ifdef HAVE_W32_SYSTEM + gnupg_fd_t hd; + char *endptr; + int base; + + if (!strcmp (fdstr, "0")) + fd = 0; + else if (!strcmp (fdstr, "1")) + fd = 1; + else if (!strcmp (fdstr, "2")) + fd = 2; + + if (fd >= 0) + { + r_syshd->type = ES_SYSHD_FD; + r_syshd->u.fd = fd; + return 0; + } + + if (!strncmp (fdstr, "0x", 2)) + { + base = 16; + fdstr += 2; + } + else + base = 10; + + gpg_err_set_errno (0); +#ifdef _WIN64 + hd = (gnupg_fd_t)strtoll (fdstr, &endptr, base); #else - (void)for_write; - return fd; + hd = (gnupg_fd_t)strtol (fdstr, &endptr, base); +#endif + if (errno != 0 || endptr == fdstr || *endptr != '\0') + return gpg_error (GPG_ERR_INV_ARG); + + r_syshd->type = ES_SYSHD_HANDLE; + r_syshd->u.handle = hd; + return 0; +#else + fd = atoi (fdstr); + r_syshd->type = ES_SYSHD_FD; + r_syshd->u.fd = fd; + return 0; #endif } @@ -589,13 +666,74 @@ check_special_filename (const char *fname, int for_write, int notranslate) for (i=0; digitp (fname+i); i++ ) ; if (!fname[i]) - return notranslate? atoi (fname) - /**/ : translate_sys2libc_fd_int (atoi (fname), for_write); + { + if (notranslate) + return atoi (fname); + else + { + es_syshd_t syshd; + + if (gnupg_parse_fdstr (fname, &syshd)) + return -1; + +#ifdef HAVE_W32_SYSTEM + if (syshd.type == ES_SYSHD_FD) + return syshd.u.fd; + else + return translate_sys2libc_fd ((gnupg_fd_t)syshd.u.handle, for_write); +#else + (void)for_write; + return syshd.u.fd; +#endif + } + } } return -1; } +/* Check whether FNAME has the form "-&nnnn", where N is a number + * representing a file. Returns GNUPG_INVALID_FD if it is not the + * case. Returns a file descriptor on POSIX, a system handle on + * Windows. */ +gnupg_fd_t +gnupg_check_special_filename (const char *fname) +{ + if (allow_special_filenames + && fname && *fname == '-' && fname[1] == '&') + { + int i; + + fname += 2; + for (i=0; digitp (fname+i); i++ ) + ; + if (!fname[i]) + { + es_syshd_t syshd; + + if (gnupg_parse_fdstr (fname, &syshd)) + return GNUPG_INVALID_FD; + +#ifdef HAVE_W32_SYSTEM + if (syshd.type == ES_SYSHD_FD) + { + if (syshd.u.fd == 0) + return GetStdHandle (STD_INPUT_HANDLE); + else if (syshd.u.fd == 1) + return GetStdHandle (STD_OUTPUT_HANDLE); + else if (syshd.u.fd == 2) + return GetStdHandle (STD_ERROR_HANDLE); + } + else + return syshd.u.handle; +#else + return syshd.u.fd; +#endif + } + } + return GNUPG_INVALID_FD; +} + /* Replacement for tmpfile(). This is required because the tmpfile function of Windows' runtime library is broken, insecure, ignores TMPDIR and so on. In addition we create a file with an inheritable @@ -805,7 +943,12 @@ gnupg_remove (const char *fname) return -1; return 0; #else - return remove (fname); + /* It is common to use /dev/null for testing. We better don't + * remove that file. */ + if (fname && !strcmp (fname, "/dev/null")) + return 0; + else + return remove (fname); #endif } @@ -963,7 +1106,7 @@ modestr_to_mode (const char *modestr, mode_t oldmode) int gnupg_mkdir (const char *name, const char *modestr) { - /* Note that gpgrt_mkdir also sets ERRNO in addition to returing an + /* Note that gpgrt_mkdir also sets ERRNO in addition to returning an * gpg-error style error code. */ return gpgrt_mkdir (name, modestr); } @@ -1148,6 +1291,19 @@ gnupg_setenv (const char *name, const char *value, int overwrite) return setenv (name, value, overwrite); #else /*!HAVE_SETENV*/ if (! getenv (name) || overwrite) +#if defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) + { + int e = _putenv_s (name, value); + + if (e) + { + gpg_err_set_errno (e); + return -1; + } + else + return 0; + } +#else { char *buf; @@ -1165,6 +1321,7 @@ gnupg_setenv (const char *name, const char *value, int overwrite) # endif return putenv (buf); } +#endif /*!HAVE_W32_SYSTEM*/ return 0; #endif /*!HAVE_SETENV*/ } @@ -1189,6 +1346,18 @@ gnupg_unsetenv (const char *name) #ifdef HAVE_UNSETENV return unsetenv (name); +#elif defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) + { + int e = _putenv_s (name, ""); + + if (e) + { + gpg_err_set_errno (e); + return -1; + } + else + return 0; + } #else /*!HAVE_UNSETENV*/ { char *buf; @@ -1819,3 +1988,22 @@ gnupg_fd_valid (int fd) close (d); return 1; } + + +/* Open a stream from FD (a file descriptor on POSIX, a system + handle on Windows), non-closed. */ +estream_t +open_stream_nc (gnupg_fd_t fd, const char *mode) +{ + es_syshd_t syshd; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = fd; +#else + syshd.type = ES_SYSHD_FD; + syshd.u.fd = fd; +#endif + + return es_sysopen_nc (&syshd, mode); +} diff --git a/common/sysutils.h b/common/sysutils.h index 7063da067..9a90d1018 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -38,12 +38,20 @@ typedef void *gnupg_fd_t; #define GNUPG_INVALID_FD ((void*)(-1)) #define INT2FD(s) ((void *)(s)) -#define FD2INT(h) ((unsigned int)(h)) +# ifdef _WIN64 +# define FD2INT(h) ((intptr_t)(h)) +# else +# define FD2INT(h) ((unsigned int)(h)) +# endif +#define FD_DBG(h) ((int)(intptr_t)(h)) +#define FD2NUM(h) ((int)(intptr_t)(h)) #else typedef int gnupg_fd_t; #define GNUPG_INVALID_FD (-1) #define INT2FD(s) (s) #define FD2INT(h) (h) +#define FD_DBG(h) (h) +#define FD2NUM(h) (h) #endif #ifdef HAVE_STAT @@ -67,14 +75,17 @@ void trap_unaligned (void); int disable_core_dumps (void); int enable_core_dumps (void); void enable_special_filenames (void); +void disable_translate_sys2libc_fd (void); + const unsigned char *get_session_marker (size_t *rlen); unsigned int get_uint_nonce (void); /*int check_permissions (const char *path,int extension,int checkonly);*/ void gnupg_sleep (unsigned int seconds); void gnupg_usleep (unsigned int usecs); -int translate_sys2libc_fd (gnupg_fd_t fd, int for_write); int translate_sys2libc_fd_int (int fd, int for_write); +gpg_error_t gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd); int check_special_filename (const char *fname, int for_write, int notranslate); +gnupg_fd_t gnupg_check_special_filename (const char *fname); FILE *gnupg_tmpfile (void); void gnupg_reopen_std (const char *pgmname); void gnupg_inhibit_set_foregound_window (int yes); @@ -108,9 +119,10 @@ gpg_error_t gnupg_inotify_watch_delete_self (int *r_fd, const char *fname); gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name); int gnupg_inotify_has_name (int fd, const char *name); +estream_t open_stream_nc (gnupg_fd_t fd, const char *mode); #ifdef HAVE_W32_SYSTEM -void gnupg_w32_set_errno (int ec); +int gnupg_w32_set_errno (int ec); void *w32_get_user_sid (void); #include "../common/w32help.h" diff --git a/common/t-b64.c b/common/t-b64.c deleted file mode 100644 index 3b6387246..000000000 --- a/common/t-b64.c +++ /dev/null @@ -1,181 +0,0 @@ -/* t-b64.c - Module tests for b64enc.c and b64dec.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 . - */ - -/* - - As of now this is only a test program for manual tests. - - */ - - - -#include -#include -#include - -#include "util.h" - -#define pass() do { ; } while(0) -#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ - __FILE__,__LINE__, (a)); \ - errcount++; \ - } while(0) - -static int verbose; -static int errcount; - -static void -test_b64enc_pgp (const char *string) -{ - gpg_error_t err; - struct b64state state; - - if (!string) - string = "a"; - - err = b64enc_start (&state, stdout, "PGP MESSAGE"); - if (err) - fail (1); - - err = b64enc_write (&state, string, strlen (string)); - if (err) - fail (2); - - err = b64enc_finish (&state); - if (err) - fail (3); - - pass (); -} - - -static void -test_b64enc_file (const char *fname) -{ - gpg_error_t err; - struct b64state state; - FILE *fp; - char buffer[50]; - size_t nread; - - fp = fname ? fopen (fname, "r") : stdin; - if (!fp) - { - fprintf (stderr, "%s:%d: can't open '%s': %s\n", - __FILE__, __LINE__, fname? fname:"[stdin]", strerror (errno)); - fail (0); - } - - err = b64enc_start (&state, stdout, "DATA"); - if (err) - fail (1); - - while ( (nread = fread (buffer, 1, sizeof buffer, fp)) ) - { - err = b64enc_write (&state, buffer, nread); - if (err) - fail (2); - } - - err = b64enc_finish (&state); - if (err) - fail (3); - - fclose (fp); - pass (); -} - -static void -test_b64dec_file (const char *fname) -{ - gpg_error_t err; - struct b64state state; - FILE *fp; - char buffer[50]; - size_t nread, nbytes; - - fp = fname ? fopen (fname, "r") : stdin; - if (!fp) - { - fprintf (stderr, "%s:%d: can't open '%s': %s\n", - __FILE__, __LINE__, fname? fname:"[stdin]", strerror (errno)); - fail (0); - } - - err = b64dec_start (&state, ""); - if (err) - fail (1); - - while ( (nread = fread (buffer, 1, sizeof buffer, fp)) ) - { - err = b64dec_proc (&state, buffer, nread, &nbytes); - if (err) - { - if (gpg_err_code (err) == GPG_ERR_EOF) - break; - fail (2); - } - else if (nbytes) - fwrite (buffer, 1, nbytes, stdout); - } - - err = b64dec_finish (&state); - if (err) - fail (3); - - fclose (fp); - pass (); -} - - - -int -main (int argc, char **argv) -{ - int do_encode = 0; - int do_decode = 0; - - if (argc) - { argc--; argv++; } - if (argc && !strcmp (argv[0], "--verbose")) - { - verbose = 1; - argc--; argv++; - } - - if (argc && !strcmp (argv[0], "--encode")) - { - do_encode = 1; - argc--; argv++; - } - else if (argc && !strcmp (argv[0], "--decode")) - { - do_decode = 1; - argc--; argv++; - } - - if (do_encode) - test_b64enc_file (argc? *argv: NULL); - else if (do_decode) - test_b64dec_file (argc? *argv: NULL); - else - test_b64enc_pgp (argc? *argv: NULL); - - return !!errcount; -} diff --git a/common/t-dotlock.c b/common/t-dotlock.c index 994ef1be3..87e62bb33 100644 --- a/common/t-dotlock.c +++ b/common/t-dotlock.c @@ -52,6 +52,7 @@ #ifdef HAVE_W32_SYSTEM #define DIM(v) (sizeof(v)/sizeof((v)[0])) + const char * w32_strerror (int ec) { @@ -174,6 +175,11 @@ strconcat (const char *s1, ...) #define PGM "t-dotlock" +static int opt_silent; + + + + #ifndef HAVE_W32_SYSTEM static volatile int ctrl_c_pending_flag; static void @@ -217,6 +223,9 @@ inf (const char *format, ...) { va_list arg_ptr; + if (opt_silent) + return; + va_start (arg_ptr, format); fprintf (stderr, PGM "[%lu]: ", (unsigned long)getpid ()); vfprintf (stderr, format, arg_ptr); @@ -225,15 +234,35 @@ inf (const char *format, ...) } +static int +lock_info_cb (dotlock_t h, void *opaque, enum dotlock_reasons reason, + const char *format, ...) +{ + va_list arg_ptr; + + va_start (arg_ptr, format); + fprintf (stderr, PGM "[%lu]: info_cb: reason %d, ", + (unsigned long)getpid (), (int)reason); + vfprintf (stderr, format, arg_ptr); + va_end (arg_ptr); + return 0; +} + + static void lock_and_unlock (const char *fname) { dotlock_t h; unsigned long usec; - h = dotlock_create (fname, 0); + h = dotlock_create (fname, DOTLOCK_PREPARE_CREATE); if (!h) die ("error creating lock file for '%s': %s", fname, strerror (errno)); + dotlock_set_info_cb (h, lock_info_cb, NULL); + h = dotlock_finish_create (h, fname); + if (!h) + die ("error finishing lock file creation for '%s': %s", + fname, strerror (errno)); inf ("lock created"); do @@ -270,6 +299,11 @@ main (int argc, char **argv) ctrl_c_pending_flag = 1; argc--; } + if (argc > 1 && !strcmp (argv[1], "--silent")) + { + opt_silent = 1; + argc--; + } if (argc > 1) fname = argv[argc-1]; diff --git a/common/t-exechelp.c b/common/t-exechelp.c index 3bf082bbb..b9a2e1e2c 100644 --- a/common/t-exechelp.c +++ b/common/t-exechelp.c @@ -25,11 +25,12 @@ #include #include "util.h" +#include "sysutils.h" #include "exechelp.h" static int verbose; - +#ifndef HAVE_W32_SYSTEM static void print_open_fds (int *array) { @@ -169,20 +170,103 @@ test_close_all_fds (void) } } +#endif + +static char buff12k[1024*12]; +static char buff4k[1024*4]; + +static void +run_server (void) +{ + estream_t fp; + int i; + char *p; + unsigned int len; + int ret; + es_syshd_t syshd; + size_t n; + off_t o; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = (HANDLE)_get_osfhandle (1); +#else + syshd.type = ES_SYSHD_FD; + syshd.u.fd = 1; +#endif + + fp = es_sysopen_nc (&syshd, "w"); + if (fp == NULL) + { + fprintf (stderr, "es_fdopen failed\n"); + exit (1); + } + + /* Fill the buffer by ASCII chars. */ + p = buff12k; + for (i = 0; i < sizeof (buff12k); i++) + if ((i % 64) == 63) + *p++ = '\n'; + else + *p++ = (i % 64) + '@'; + + len = sizeof (buff12k); + + ret = es_write (fp, (void *)&len, sizeof (len), NULL); + if (ret) + { + fprintf (stderr, "es_write (1) failed\n"); + exit (1); + } + + es_fflush (fp); + + o = 0; + n = len; + + while (1) + { + size_t n0, n1; + + n0 = n > 4096 ? 4096 : n; + memcpy (buff4k, buff12k + o, n0); + + ret = es_write (fp, buff4k, n0, &n1); + if (ret || n0 != n1) + { + fprintf (stderr, "es_write (2) failed\n"); + exit (1); + } + + o += n0; + n -= n0; + if (n == 0) + break; + } + + es_fclose (fp); + exit (0); +} int main (int argc, char **argv) { if (argc) - { argc--; argv++; } + { + argc--; argv++; + } if (argc && !strcmp (argv[0], "--verbose")) { verbose = 1; argc--; argv++; } + if (argc && !strcmp (argv[0], "--server")) + run_server (); +#ifndef HAVE_W32_SYSTEM test_close_all_fds (); +#endif return 0; } diff --git a/common/t-gettime.c b/common/t-gettime.c index 13cb1a2f7..76c305204 100644 --- a/common/t-gettime.c +++ b/common/t-gettime.c @@ -43,6 +43,56 @@ static int errcount; #define INVALID ((time_t)(-1)) +static void +test_scan_secondsstr (void) +{ + struct { const char *string; u32 expected; } array [] = { + { "", 0 }, + { "0", 0 }, + { " 0", 0 }, + { " 0x", 0 }, + { " 1", 1 }, + { "-1", 0 }, + { " -1", 0 }, + { "2", 2 }, + { "11", 11 }, + { "011", 11 }, + { "3600 ", 3600 }, + { "65535", 65535 }, + { "65536", 65536 }, + { "65537", 65537 }, + { "4294967289", 4294967289 }, + { "4294967290", 4294967290 }, + { "4294967293", 4294967293 }, + { "4294967295", 4294967294 }, + { "4294967296", 4294967294 }, + { "4294967297", 4294967294 }, + { "4294967298", 4294967294 }, + { "4294967299", 4294967294 }, + { "4294967300", 4294967294 }, + { "5294967300", 4294967294 }, + { "9999999999", 4294967294 }, + { "99999999999",4294967294 }, + { NULL, 0 } + }; + int idx; + u32 val; + + for (idx=0; array[idx].string; idx++) + { + val = scan_secondsstr (array[idx].string); + if (val != array[idx].expected ) + { + fail (idx); + if (verbose) + fprintf (stderr, "string '%s' exp: %ld got: %ld\n", + array[idx].string, (unsigned long)array[idx].expected, + (unsigned long)val); + } + } +} + + static void test_isotime2epoch (void) { @@ -103,7 +153,6 @@ test_isotime2epoch (void) } - static void test_string2isotime (void) { @@ -269,6 +318,7 @@ main (int argc, char **argv) if (argc > 1 && !strcmp (argv[1], "--verbose")) verbose = 1; + test_scan_secondsstr (); test_isotime2epoch (); test_string2isotime (); test_isodate_human_to_tm (); diff --git a/common/t-helpfile.c b/common/t-helpfile.c index 0e2c79f2d..d1e7f547d 100644 --- a/common/t-helpfile.c +++ b/common/t-helpfile.c @@ -39,6 +39,7 @@ int main (int argc, char **argv) { char *result; + unsigned int flags = 0; if (argc) { argc--; argv++; } @@ -48,17 +49,35 @@ main (int argc, char **argv) verbose = 1; argc--; argv++; } + if (argc && !strcmp (argv[0], "--env")) + { + flags |= GET_TEMPLATE_SUBST_ENVVARS; + argc--; argv++; + } + if (argc && !strcmp (argv[0], "--crlf")) + { + flags |= GET_TEMPLATE_CRLF; + argc--; argv++; + } + if (argc != 2) + { + fprintf (stderr, "Usage: t-helpfile [--env] [--crlf] domain key\n"); + exit (2); + } - result = gnupg_get_help_string (argc? argv[0]:NULL, 0); + + result = gnupg_get_template (argv[0], argv[1], flags, NULL); if (!result) { fprintf (stderr, - "Error: nothing found for '%s'\n", argc?argv[0]:"(null)"); + "Error: nothing found for '%s' in domain '%s'\n", + argv[1], argv[0]); errcount++; } else { - printf ("key '%s' result='%s'\n", argc?argv[0]:"(null)", result); + printf ("domain '%s' key '%s' result='%s'\n", + argv[0], argv[1], result); xfree (result); } diff --git a/common/t-iobuf.c b/common/t-iobuf.c index bdeab99a4..9aa0720f6 100644 --- a/common/t-iobuf.c +++ b/common/t-iobuf.c @@ -1,3 +1,36 @@ +/* t-iobuf.c - Simple module test for iobuf.c + * 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 . + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) + */ + +/* The whole code here does not very fill into our general test frame + * work pattern. But let's keep it as it is. */ + #include #include #include @@ -7,6 +40,20 @@ #include "iobuf.h" #include "stringhelp.h" + +static void * +xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + { + fprintf (stderr, "t-iobuf: out of core\n"); + abort (); + } + return p; +} + + /* Return every other byte. In particular, reads two bytes, returns the second one. */ static int @@ -86,7 +133,7 @@ static struct content_filter_state * content_filter_new (const char *buffer) { struct content_filter_state *state - = malloc (sizeof (struct content_filter_state)); + = xmalloc (sizeof (struct content_filter_state)); state->pos = 0; state->len = strlen (buffer); @@ -215,8 +262,7 @@ main (int argc, char *argv[]) allocate a buffer that is 5 bytes long, then no reallocation should be required. */ size = 5; - buffer = malloc (size); - assert (buffer); + buffer = xmalloc (size); max_len = 100; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 4); @@ -229,7 +275,7 @@ main (int argc, char *argv[]) requires 6 bytes of storage. We pass a buffer that is 5 bytes large and we allow the buffer to be grown. */ size = 5; - buffer = malloc (size); + buffer = xmalloc (size); max_len = 100; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 5); @@ -243,7 +289,7 @@ main (int argc, char *argv[]) requires 7 bytes of storage. We pass a buffer that is 5 bytes large and we don't allow the buffer to be grown. */ size = 5; - buffer = malloc (size); + buffer = xmalloc (size); max_len = 5; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 4); diff --git a/common/t-recsel.c b/common/t-recsel.c index 2d5a95d25..6ee8bed84 100644 --- a/common/t-recsel.c +++ b/common/t-recsel.c @@ -225,9 +225,27 @@ run_test_2 (void) if (!recsel_select (se, test_2_getval, NULL)) fail (0, 0); FREEEXPR(); - ADDEXPR ("uid =~ @"); + ADDEXPR ("uid !~ @"); + if (recsel_select (se, test_2_getval, NULL)) + fail (0, 0); + + FREEEXPR(); + ADDEXPR ("uid =~ foo@"); if (!recsel_select (se, test_2_getval, NULL)) fail (0, 0); + FREEEXPR(); + ADDEXPR ("uid =~ oo@"); + if (!recsel_select (se, test_2_getval, NULL)) + fail (0, 0); + FREEEXPR(); + /* Again but with left anchored substring. */ + ADDEXPR ("-^ uid =~ foo@"); + if (!recsel_select (se, test_2_getval, NULL)) + fail (0, 0); + FREEEXPR(); + ADDEXPR ("-^ uid =~ oo@"); + if (recsel_select (se, test_2_getval, NULL)) + fail (0, 0); FREEEXPR(); ADDEXPR ("keyid == 0x12345678"); @@ -306,7 +324,7 @@ run_test_2 (void) FREEEXPR(); ADDEXPR ("nothing -z"); - if (!recsel_select (se, test_2_getval, NULL)) + if (recsel_select (se, test_2_getval, NULL)) fail (0, 0); FREEEXPR(); ADDEXPR ("nothing -n"); @@ -334,7 +352,7 @@ run_test_2 (void) FREEEXPR(); ADDEXPR ("nothing -f"); - if (!recsel_select (se, test_2_getval, NULL)) + if (recsel_select (se, test_2_getval, NULL)) fail (0, 0); FREEEXPR(); ADDEXPR ("nothing -t"); @@ -369,7 +387,7 @@ run_test_2 (void) fail (0, 0); FREEEXPR(); - ADDEXPR ("letter -f"); + ADDEXPR ("letters -f"); if (!recsel_select (se, test_2_getval, NULL)) fail (0, 0); FREEEXPR(); diff --git a/common/t-strlist.c b/common/t-strlist.c index fdbeb9b99..65fc52420 100644 --- a/common/t-strlist.c +++ b/common/t-strlist.c @@ -72,6 +72,194 @@ test_strlist_rev (void) } +static void +test_tokenize_to_strlist (void) +{ + struct { + const char *s; + const char *delim; + int error_expected; + const char *items_expected[10]; + } tv[] = { + { + "", ":", + 1, { NULL } + }, + { + "a", ":", + 0, { "a", NULL } + }, + { + ":", ":", + 1, { NULL } + }, + { + "::", ":", + 1, { NULL } + }, + { + "a:b:c", ":", + 0, { "a", "b", "c", NULL } + }, + { + "a:b:", ":", + 0, { "a", "b", NULL } + }, + { + "a:b", ":", + 0, { "a", "b", NULL } + }, + { + "aa:b:cd", ":", + 0, { "aa", "b", "cd", NULL } + }, + { + "aa::b:cd", ":", + 0, { "aa", "b", "cd", NULL } + }, + { + "::b:cd", ":", + 0, { "b", "cd", NULL } + }, + { + "aa: : b:cd ", ":", + 0, { "aa", "b", "cd", NULL } + }, + { + " aa: : b: cd ", ":", + 0, { "aa", "b", "cd", NULL } + }, + { + " :", ":", + 1, { NULL } + }, + { + " : ", ":", + 1, { NULL } + }, + { + ": ", ":", + 1, { NULL } + }, + { + ": x ", ":", + 0, { "x", NULL } + }, + { + "a:bc:cde:fghi:jklmn::foo:", ":", + 0, { "a", "bc", "cde", "fghi", "jklmn", "foo", NULL } + }, + { + ",a,bc,,def,", ",", + 0, { "a", "bc", "def", NULL } + }, + { + " a ", " ", + 0, { "a", NULL } + }, + { + " ", " ", + 1, { NULL } + }, + { + "a:bc:c de:fg hi:jklmn::foo :", ":", + 0, { "a", "bc", "c de", "fg hi", "jklmn", "foo", NULL } + }, + { + "", " ", + 1, { NULL } + } + }; + const char *prefixes[3] = { "abc", "bcd", "efg" }; + int tidx; + int nprefixes; /* Number of items in already in the list. */ + strlist_t list = NULL; + + for (nprefixes = 0; nprefixes < DIM (prefixes); nprefixes++) + for (tidx = 0; tidx < DIM(tv); tidx++) + { + int item_count_expected; + int i; + strlist_t sl, newitems; + + for (item_count_expected = 0; + tv[tidx].items_expected[item_count_expected]; + item_count_expected++) + ; + + /* printf ("np=%d testing %d \"%s\" delim=\"%s\"\n", */ + /* nprefixes, tidx, tv[tidx].s, tv[tidx].delim); */ + for (i=0; i < nprefixes; i++) + append_to_strlist (&list, prefixes[i]); + + newitems = tokenize_to_strlist (&list, tv[tidx].s, tv[tidx].delim); + if (!newitems) + { + if (gpg_err_code_from_syserror () == GPG_ERR_ENOENT + && tv[tidx].error_expected) + { + /* Good. But need to check the prefixes. */ + for (sl=list, i=0; i < nprefixes; i++, sl=sl->next) + { + if (!sl || strcmp (prefixes[i], sl->d)) + { + printf ("For item %d prefix item %d, expected '%s'\n", + tidx, i, prefixes[i]); + fail (tidx * 1000 + 40 + i + 1); + } + } + } + else + fail (tidx * 1000); + } + else if (tv[tidx].error_expected) + { + printf ("got items"); + for (sl = list; sl; sl = sl->next) + printf (" \"%s\"", sl->d); + printf ("\n"); + fail (tidx * 1000); + } + else + { + if (strlist_length (list) != nprefixes + item_count_expected) + fail (tidx * 1000); + else + { + for (sl=list, i=0; i < nprefixes; i++, sl=sl->next) + { + if (!sl || strcmp (prefixes[i], sl->d)) + { + printf ("For item %d prefix item %d, expected '%s'\n", + tidx, i, prefixes[i]); + fail (tidx * 1000 + 50 + i + 1); + } + } + for (i=0; i < item_count_expected; i++, sl=sl->next) + { + if (!sl) + { + printf ("No item at item index %d\n", i); + fail (tidx * 1000 + i + 0); + break; + } + if (strcmp (tv[tidx].items_expected[i], sl->d)) + { + printf ("For item %d, expected '%s', but got '%s'\n", + i, tv[tidx].items_expected[i], sl->d); + fail (tidx * 1000 + 10 + i + 1); + } + } + } + } + + free_strlist (list); + list = NULL; + } +} + + + int main (int argc, char **argv) { @@ -79,6 +267,7 @@ main (int argc, char **argv) (void)argv; test_strlist_rev (); + test_tokenize_to_strlist (); return 0; } diff --git a/common/t-support.h b/common/t-support.h index 7aa46c00c..aa1b560fc 100644 --- a/common/t-support.h +++ b/common/t-support.h @@ -31,6 +31,8 @@ #ifndef GNUPG_COMMON_T_SUPPORT_H #define GNUPG_COMMON_T_SUPPORT_H 1 +#ifndef LEAN_T_SUPPORT + #ifdef GCRYPT_VERSION #error The regression tests should not include with gcrypt.h #endif @@ -45,11 +47,6 @@ # define getenv(a) (NULL) #endif -#ifndef DIM -# define DIM(v) (sizeof(v)/sizeof((v)[0])) -# define DIMof(type,member) DIM(((type *)0)->member) -#endif - /* Replacement prototypes. */ void *gcry_xmalloc (size_t n); @@ -65,6 +62,12 @@ void gcry_free (void *a); #define xstrdup(a) gcry_xstrdup ( (a) ) #define xfree(a) gcry_free ( (a) ) +#endif /* LEAN_T_SUPPORT */ + +#ifndef DIM +# define DIM(v) (sizeof(v)/sizeof((v)[0])) +# define DIMof(type,member) DIM(((type *)0)->member) +#endif /* Macros to print the result of a test. */ #define pass() do { ; } while(0) diff --git a/common/tlv-builder.c b/common/tlv-builder.c index 59e2691e0..0fa5fc2cc 100644 --- a/common/tlv-builder.c +++ b/common/tlv-builder.c @@ -95,7 +95,7 @@ ensure_space (tlv_builder_t tb) * element is described by CLASS, TAG, VALUE, and VALUEEN. CLASS and * TAG must describe a primitive element and (VALUE,VALUELEN) specify * its value. The value is a pointer and its object must not be - * changed as long as the instance TB exists. For a TAG_NULL no vlaue + * changed as long as the instance TB exists. For a TAG_NULL no value * is expected. Errors are not returned but recorded for later * retrieval. */ void diff --git a/common/tlv-parser.c b/common/tlv-parser.c new file mode 100644 index 000000000..b52cc7a21 --- /dev/null +++ b/common/tlv-parser.c @@ -0,0 +1,827 @@ +/* tlv-parser.c - Parse BER encoded objects + * Copyright (C) 2023, 2024 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 . + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#include +#include +#include +#include + +#include "util.h" +#include "tlv.h" + + +#define TLV_MAX_DEPTH 25 + + + +/* An object to control the ASN.1 parsing. */ +struct tlv_parser_s +{ + /* The original buffer with the entire pkcs#12 object and its length. */ + unsigned char *origbuffer; + size_t origbufsize; + + /* The original offset for debugging. */ + size_t origoff; + + /* Here we keep a copy of the former TLV. This is returned by + * tlv_parser_release. */ + tlv_parser_t lasttlv; + + /* The current buffer we are working on and its length. */ + unsigned char *buffer; + size_t bufsize; + + size_t crammed; /* 0 or actual length of crammed octet strings. */ + int in_ndef; /* Flag indicating that we are in a NDEF. */ + int pending; /* The last tlv_next has not yet been processed. */ + + struct tag_info ti; /* The current tag. */ + gpg_error_t lasterr; /* Last error from tlv function. */ + const char *lastfunc;/* Name of last called function. */ + int verbosity; /* Arg from tlv_parser_new. */ + + unsigned int stacklen; /* Used size of the stack. */ + struct { + unsigned char *buffer; /* Saved value of BUFFER. */ + size_t bufsize; /* Saved value of BUFSIZE. */ + size_t length; /* Length of the container (ti.length). */ + size_t crammed; /* Saved CRAMMED value. */ + int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */ + } stack[TLV_MAX_DEPTH]; +}; + + +static size_t cram_octet_string (tlv_parser_t tlv, int testmode); + + + +void +_tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv) +{ + struct tag_info *ti; + + if (!tlv || tlv->verbosity < 2) + return; + + ti = &tlv->ti; + + log_debug ("%s:%d: %zu@%04zu class=%d tag=%lu %c len=%zu%s nhdr=%zu\n", + text, lno, tlv->origoff, tlv_parser_offset (tlv) - ti->nhdr, + ti->class, ti->tag, ti->is_constructed?'c':'p', + ti->length,ti->ndef?" ndef":"", ti->nhdr); +} + + +void +_tlv_parser_dump_state (const char *text, const char *text2, + int lno, tlv_parser_t tlv) +{ + if (!tlv || tlv->verbosity < 2) + return; + + log_debug ("p12_parse:%s%s%s:%d: %zu@%04zu lvl=%u %s\n", + text, + text2? "/":"", text2? text2:"", + lno, tlv->origoff, tlv_parser_offset (tlv), + tlv->stacklen, + tlv->in_ndef? " in-ndef":""); +} + + +static void +dump_to_file (const void *s, size_t n, const char *name) +{ +#if 0 + FILE *fp; + char fname[100]; + static int fcount; + + snprintf (fname, sizeof fname, "tmp-%03d-%s", ++fcount, name); + log_debug ("dumping %zu bytes to '%s'\n", n, fname); + fp = fopen (fname, "wb"); + if (!fp || fwrite (s, n, 1, fp) != 1) + exit (2); + fclose (fp); +#else + (void)s; + (void)n; + (void)name; +#endif +} + + +/* Parse the buffer at the address BUFFER which is of SIZE and return + * the tag and the length part from the TLV triplet. Update BUFFER + * and SIZE on success. Checks that the encoded length does not + * exhaust the length of the provided buffer. */ +static int +parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + gpg_error_t err; + int tag; + + err = parse_ber_header (buffer, size, + &ti->class, &tag, + &ti->is_constructed, &ti->ndef, + &ti->length, &ti->nhdr); + if (err) + return err; + if (tag < 0) + return gpg_error (GPG_ERR_EOVERFLOW); + ti->tag = tag; + + if (ti->length > *size) + { + /* data larger than buffer. */ + log_debug ("%s: ti->length=%zu for a buffer of size=%zu\n", + __func__, ti->length, *size); + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + } + + return 0; +} + +/* Public version of parse_tag. */ +gpg_error_t +tlv_parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti) +{ + return parse_tag (buffer, size, ti); +} + + +/* Create a new TLV object. */ +tlv_parser_t +_tlv_parser_new (const unsigned char *buffer, size_t bufsize, + int verbosity, tlv_parser_t lasttlv, int lno) +{ + tlv_parser_t tlv; + + if (verbosity > 1) + log_debug ("%s:%d: %zu@%zu (%p,%zu)\n", __func__, lno, + lasttlv?lasttlv->origoff:0, tlv_parser_offset (lasttlv), + buffer, bufsize); + + tlv = xtrycalloc (1, sizeof *tlv); + if (tlv) + { + char *mybuf = xtrymalloc ( bufsize + 1); + if (!mybuf) + { + xfree (tlv); + return NULL; + } + memcpy (mybuf, buffer, bufsize); + mybuf[bufsize] = 0; + tlv->origbuffer = mybuf; + tlv->origbufsize = bufsize; + tlv->origoff = tlv_parser_offset (lasttlv); + tlv->buffer = mybuf; + tlv->bufsize = bufsize; + tlv->crammed = 0; + tlv->verbosity = verbosity; + tlv->lasttlv = lasttlv; + dump_to_file (mybuf, bufsize, "context"); + } + return tlv; +} + + +/* Free the TLV object and returns the last TLV object stored in this + * TLV. */ +tlv_parser_t +_tlv_parser_release (tlv_parser_t tlv, int lno) +{ + tlv_parser_t result; + + if (!tlv) + return NULL; + result = tlv->lasttlv; + if (tlv->verbosity > 1) + { + if (result) + log_debug ("%s:%d: done; returning last TLV %zu@%zu (%p,%zu)\n", + __func__, lno, result->origoff, + tlv_parser_offset (result), result->buffer, result->bufsize); + else + log_debug ("%s:%d: done\n", __func__, lno); + } + xfree (tlv->origbuffer); + xfree (tlv); + return result; +} + + +/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */ +static gpg_error_t +_tlv_push (tlv_parser_t tlv) +{ + + /* Right now our pointer is at the value of the current container. + * We push that info onto the stack. */ + if (tlv->stacklen >= TLV_MAX_DEPTH) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY)); + tlv->stack[tlv->stacklen].buffer = tlv->buffer; + tlv->stack[tlv->stacklen].bufsize = tlv->bufsize; + tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef; + tlv->stack[tlv->stacklen].length = tlv->ti.length; + tlv->stack[tlv->stacklen].crammed = tlv->crammed; + tlv->stacklen++; + + tlv->in_ndef = tlv->ti.ndef; + + /* We set the size of the buffer to the TLV length if it is known or + * else to the size of the remaining entire buffer. */ + if (tlv->in_ndef) + { + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); + } + else + tlv->bufsize = tlv->ti.length; + + _tlv_parser_dump_state (__func__, NULL, 0, tlv); + return 0; +} + + +/* Helper for tlv_next. */ +static gpg_error_t +_tlv_pop (tlv_parser_t tlv) +{ + size_t length; + + /* We reached the end of a container, either due to the size limit + * or due to an end tag. Now we pop the last container so that we + * are positioned at the value of the last container. */ + if (!tlv->stacklen) + return gpg_error (GPG_ERR_EOF); + + tlv->stacklen--; + tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef; + length = tlv->ti.length = tlv->stack[tlv->stacklen].length; + tlv->crammed = tlv->stack[tlv->stacklen].crammed; + if (tlv->in_ndef) + { + /* We keep buffer but adjust bufsize to the end of the origbuffer. */ + if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize) + return (tlv->lasterr = gpg_error (GPG_ERR_BUG)); + tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer); + } + else + { + tlv->buffer = tlv->stack[tlv->stacklen].buffer; + tlv->bufsize = tlv->stack[tlv->stacklen].bufsize; + if (length > tlv->bufsize) + { + if (tlv->verbosity > 1) + log_debug ("%s: container larger than buffer (%zu/%zu)\n", + __func__, length, tlv->bufsize); + return gpg_error (GPG_ERR_INV_BER); + } + tlv->buffer += length; + tlv->bufsize -= length; + + } + _tlv_parser_dump_state (__func__, NULL, 0, tlv); + return 0; +} + + +/* Parse the next tag and value. Also detect the end of a + * container. The caller should use the tlv_next macro. */ +gpg_error_t +_tlv_parser_next (tlv_parser_t tlv, unsigned int flag, int lno) +{ + gpg_error_t err; + const unsigned char *buffer; + size_t save_bufsize; + const unsigned char *save_buffer; + int i; + + tlv->lasterr = 0; + tlv->lastfunc = __func__; + + if (tlv->pending) + { + tlv->pending = 0; + if (tlv->verbosity > 1) + log_debug ("%s:%d: skipped\n", __func__, lno); + return 0; + } + + if (tlv->verbosity > 1) + log_debug ("%s:%d: called (%p,%zu)\n", __func__, lno, + tlv->buffer, tlv->bufsize); + /* If we are at the end of an ndef container pop the stack. */ + if (!tlv->in_ndef && !tlv->bufsize) + { + if (tlv->verbosity > 1) + for (i=0; i < tlv->stacklen; i++) + log_debug ("%s: stack[%d] (%p,@%zu,%zu) len=%zu (%zu) %s\n", + __func__, i, + tlv->stack[i].buffer, + tlv->stack[i].buffer - tlv->origbuffer, + tlv->stack[i].bufsize, + tlv->stack[i].length, + tlv->stack[i].crammed, + tlv->stack[i].in_ndef? " ndef":""); + do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && !tlv->bufsize); + + if (err) + return (tlv->lasterr = err); + if (tlv->verbosity > 1) + log_debug ("%s: container(s) closed due to size (lvl=%d)\n", + __func__, tlv->stacklen); + } + + again: + /* Get the next tag. */ + save_buffer = buffer = tlv->buffer; + save_bufsize = tlv->bufsize; + err = parse_tag (&buffer, &tlv->bufsize, &tlv->ti); + tlv->buffer = (unsigned char *)buffer; + if (err) + { + if (tlv->verbosity > 1) + { + log_debug ("%s: reading tag returned err=%d\n", __func__, err); + log_printhex (save_buffer, save_bufsize > 40? 40: save_bufsize, + "%s: data was\n", __func__); + dump_to_file (tlv->origbuffer, save_buffer - tlv->origbuffer, + "parseerr"); + } + return err; + } + + if ( ( (tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING) + || ((flag & TLV_PARSER_FLAG_T5793) + && tlv->ti.class == CLASS_CONTEXT && tlv->ti.tag == 0)) + && tlv->ti.is_constructed && cram_octet_string (tlv, 1)) + { + if (tlv->verbosity > 1) + log_debug ("%s: cramming %s\n", __func__, + tlv->ti.tag? "constructed octet strings":"for Mozilla bug"); + if (!cram_octet_string (tlv, 0)) + return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER)); + } + + /* If there is an end tag in an ndef container pop the stack. Also + * pop other containers which are fully consumed. */ + if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL + && !tlv->ti.tag && !tlv->ti.is_constructed)) + { + do + err = _tlv_pop (tlv); + while (!err && !tlv->in_ndef && !tlv->bufsize); + if (err) + return (tlv->lasterr = err); + if (tlv->verbosity > 1) + log_debug ("%s: container(s) closed due to end tag (lvl=%d)\n", + __func__, tlv->stacklen); + goto again; + } + + _tlv_parser_dump_tag (__func__, lno, tlv); + return 0; +} + + +/* Return the current neting level of the TLV object. */ +unsigned int +tlv_parser_level (tlv_parser_t tlv) +{ + return tlv? tlv->stacklen : 0; +} + +/* Returns the current offset of the parser. */ +size_t +tlv_parser_offset (tlv_parser_t tlv) +{ + return tlv? (size_t)(tlv->buffer - tlv->origbuffer) : 0; +} + + +/* Return a string with the last function used. If TLV is NULL an + * empty string is returned. */ +const char * +tlv_parser_lastfunc (tlv_parser_t tlv) +{ + return tlv? tlv->lastfunc:""; +} + + +const char * +tlv_parser_lasterrstr (tlv_parser_t tlv) +{ + return tlv? gpg_strerror (tlv->lasterr) : "tlv parser not yet initialized"; +} + + +/* Set a flag to indicate that the last tlv_next has not yet been + * consumed. */ +void +tlv_parser_set_pending (tlv_parser_t tlv) +{ + tlv->pending = 1; +} + + +/* Return the length of the last read tag. If with_header is 1 the + * lengtb of the header is added to the returned length. */ +size_t +tlv_parser_tag_length (tlv_parser_t tlv, int with_header) +{ + if (with_header) + return tlv->ti.length + tlv->ti.nhdr; + else + return tlv->ti.length; +} + + +/* Skip over the value of the current tag. Does not yet work for ndef + * containers. */ +void +tlv_parser_skip (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + log_assert (tlv->bufsize >= tlv->ti.length); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; +} + + +/* Expect that the current tag is a sequence and setup the context for + * processing. */ +gpg_error_t +tlv_expect_sequence (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a context tag and setup the context + * for processing. The tag of the context is returned at R_TAG. */ +gpg_error_t +tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + *r_tag = tlv->ti.tag; + return _tlv_push (tlv); +} + + +/* Expect that the current tag is a SET and setup the context for + * processing. */ +gpg_error_t +tlv_expect_set (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET + && tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return _tlv_push (tlv); +} + + +/* Expect an object of CLASS with TAG and store its value at + * (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value is not allocated but points into + * TLV. */ +gpg_error_t +tlv_expect_object (tlv_parser_t tlv, int class, int tag, + unsigned char const **r_data, size_t *r_datalen) +{ + const unsigned char *p; + size_t n; + int needpush = 0; + + tlv->lastfunc = __func__; + /* Note that the parser has already crammed the octet strings for a + * [0] to workaround the Mozilla bug. */ + if (!(tlv->ti.class == class && tlv->ti.tag == tag)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + n = tlv->ti.length; + if (!n && tlv->ti.ndef) + { + n = tlv->bufsize; + needpush = 1; + } + else if (!tlv->ti.length) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (tlv->verbosity > 1) + log_debug ("%s: %zu@%zu %c len=%zu (%zu) bufsize=%zu of %zu\n", + __func__, + tlv->origoff, tlv_parser_offset (tlv), + tlv->ti.is_constructed? 'c':'p', + n, tlv->crammed, + tlv->bufsize, tlv->origbufsize); + + if (r_data) + *r_data = p; + if (r_datalen) + *r_datalen = tlv->crammed? tlv->crammed : n; + + if (needpush) + return _tlv_push (tlv); + + if (!(tlv->bufsize >= n)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += n; + tlv->bufsize -= n; + tlv->crammed = 0; + return 0; +} + + +/* Expect that the current tag is an object string and store its value + * at (R_DATA,R_DATALEN). Then skip over its value to the next tag. + * Note that the stored value are not allocated but point into TLV. */ +gpg_error_t +tlv_expect_octet_string (tlv_parser_t tlv, + unsigned char const **r_data, size_t *r_datalen) +{ + size_t n; + + tlv->lastfunc = __func__; + /* The parser has already crammed constructed octet strings. */ + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + if (!(n=tlv->ti.length) || tlv->ti.ndef ) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (tlv->verbosity > 1) + log_debug ("%s: %zu@%zu %c len=%zu (%zu) bufsize=%zu of %zu\n", + __func__, + tlv->origoff, tlv_parser_offset (tlv), + tlv->ti.is_constructed? 'c':'p', + n, tlv->crammed, + tlv->bufsize, tlv->origbufsize); + + if (r_data) + *r_data = tlv->buffer; + if (r_datalen) + *r_datalen = tlv->crammed? tlv->crammed : tlv->ti.length; + + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + tlv->crammed = 0; + return 0; +} + + +/* Expect that the current tag is an integer and return its value at + * R_VALUE. Then skip over its value to the next tag. */ +gpg_error_t +tlv_expect_integer (tlv_parser_t tlv, int *r_value) +{ + const unsigned char *p; + size_t n; + int value; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + /* We currently support only positive values. */ + if ((*p & 0x80)) + return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE)); + + for (value = 0; n; n--) + { + value <<= 8; + value |= (*p++) & 0xff; + if (value < 0) + return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW)); + } + *r_value = value; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO + * is set a value of 0 is ignored and R_VALUE not changed and the + * function returns GPG_ERR_FALSE. No check for negative encoded + * integers is done because the old code here worked the same and we + * can't foreclose invalid encoded PKCS#12 stuff - after all it is + * PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */ +#ifdef GCRYPT_VERSION +gpg_error_t +tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero, + gcry_mpi_t *r_value) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + if (ignore_zero && n == 1 && !*p) + return gpg_error (GPG_ERR_FALSE); + + return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL); +} +#endif /*GCRYPT_VERSION*/ + + +/* Expect that the current tag is an object id and store its value at + * (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note + * that the stored value is not allocated but points into TLV. */ +gpg_error_t +tlv_expect_object_id (tlv_parser_t tlv, + unsigned char const **r_oid, size_t *r_oidlen) +{ + const unsigned char *p; + size_t n; + + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID + && !tlv->ti.is_constructed)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + p = tlv->buffer; + if (!(n=tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + + *r_oid = p; + *r_oidlen = tlv->ti.length; + if (!(tlv->bufsize >= tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT)); + tlv->buffer += tlv->ti.length; + tlv->bufsize -= tlv->ti.length; + return 0; +} + + +/* Expect a NULL tag. */ +gpg_error_t +tlv_expect_null (tlv_parser_t tlv) +{ + tlv->lastfunc = __func__; + if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_NULL + && !tlv->ti.is_constructed && !tlv->ti.length)) + return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ)); + return 0; +} + + +/* Given a BER encoded constructed octet string like the example below + * from Mozilla Firefox 1.0.4 (which actually exports certs as single + * byte chunks of octet strings) in the buffer described by TLV. + * Although the example uses an ndef for the length of the constructed + * octet string, a fixed length is also allowed. + * + * 24 NDEF: OCTET STRING + * 04 1: OCTET STRING -- TLV->buffer points to here + * : 30 + * 04 1: OCTET STRING + * : 80 + * [...] + * 04 2: OCTET STRING + * : 00 00 + * : } -- This denotes a Null tag and are the last + * -- two bytes in INPUT. + * + * Turn it into a primitive octet string of this form: + * + * 24 2: OCTET STRING + * : 30 80 + * + * and fill it up with FE to the original length. Unless TESTMODE is + * true the TLV object including the the member and the data is + * adjusted accordingly; however the intiial tag is not changed (in + * the example the "24 NDEF") because this is not needed anymore. + * + * On error 0 is returned and in this case the buffer might have + * already been modified and thus the caller should better stop + * parsing - unless TESTMODE was used. */ +static size_t +cram_octet_string (tlv_parser_t tlv, int testmode) +{ + gpg_error_t err; + size_t totallen; /* Length of the non-crammed octet strings. */ + size_t crammedlen; /* Length of the crammed octet strings. */ + const unsigned char *s, *save_s; + unsigned char *d; + size_t n, save_n; + struct tag_info ti; + + if (tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING) + ; /* Okay. */ + else if (tlv->ti.class == CLASS_CONTEXT && tlv->ti.tag == 0) + ; /* Workaround for Mozilla bug; see T5793 */ + else + return 0; /* Oops - we should not have been called. */ + if (!tlv->ti.is_constructed) + return 0; /* Oops - Not a constructed octet string. */ + if (!tlv->ti.ndef && tlv->ti.length < 4) + return 0; /* Fixed length but too short. */ + + /* Let S point to the first octet string chunk. */ + s = tlv->buffer; + n = tlv->ti.ndef? tlv->bufsize : tlv->ti.length; + + d = (unsigned char *)s; + totallen = crammedlen = 0; + while (n) + { + save_s = s; + save_n = n; + if ((err=parse_tag (&s, &n, &ti))) + { + if (tlv->verbosity > 1) + { + log_debug ("%s: parse_tag(n=%zu) failed : %s\n", + __func__, save_n, gpg_strerror (err)); + log_printhex (save_s, save_n > 40? 40:save_n, "%s: data was", + __func__); + } + return 0; + } + if (tlv->verbosity > 1) + log_debug ("%s:%s ti.ndef=%d ti.tag=%lu ti.length=%zu (n %zu->%zu)\n", + __func__, testmode?"test:":"", + ti.ndef, ti.tag, ti.length, save_n, n); + if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING + && !ti.ndef && !ti.is_constructed) + { + if (!testmode) + memmove (d, s, ti.length); + d += ti.length; /* Update destination */ + totallen += ti.length + ti.nhdr; + crammedlen += ti.length; + s += ti.length; /* Skip to next tag. */ + n -= ti.length; + } + else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed) + { + totallen += ti.nhdr; + break; /* EOC - Ready */ + } + else + return 0; /* Invalid data. */ + } + if (!testmode) + { + memset (d, '\xfe', totallen - crammedlen); + tlv->ti.length = totallen; + tlv->ti.is_constructed = 0; + tlv->ti.ndef = 0; + tlv->crammed = crammedlen; + if (tlv->verbosity > 1) + { + log_debug ("%s: crammed length is %zu\n", __func__, crammedlen); + log_debug ("%s: total length is %zu\n", __func__, totallen); + } + dump_to_file (tlv->buffer, totallen, "crammed"); + } + return totallen; +} diff --git a/common/tlv.c b/common/tlv.c index 4cc1dc7cf..c77f4cc4f 100644 --- a/common/tlv.c +++ b/common/tlv.c @@ -150,13 +150,16 @@ find_tlv_unchecked (const unsigned char *buffer, size_t length, /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag - and the length part from the TLV triplet. Update BUFFER and SIZE - on success. */ + * and the length part from the TLV triplet. Update BUFFER and SIZE + * on success. Note that this function does not check that the value + * fits into the provided buffer; this allows one to work on the TL part + * of a TLV. */ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, int *r_ndef, - size_t *r_length, size_t *r_nhdr){ + size_t *r_length, size_t *r_nhdr) +{ int c; unsigned long tag; const unsigned char *buf = *buffer; diff --git a/common/tlv.h b/common/tlv.h index e371ca57e..cfd615003 100644 --- a/common/tlv.h +++ b/common/tlv.h @@ -71,10 +71,22 @@ enum tlv_tag_type { TAG_BMP_STRING = 30 }; +struct tag_info +{ + int class; + int is_constructed; + unsigned long tag; + size_t length; /* length part of the TLV */ + size_t nhdr; + int ndef; /* It is an indefinite length */ +}; struct tlv_builder_s; typedef struct tlv_builder_s *tlv_builder_t; +struct tlv_parser_s; +typedef struct tlv_parser_s *tlv_parser_t; + /*-- tlv.c --*/ /* Locate a TLV encoded data object in BUFFER of LENGTH and return a @@ -94,7 +106,7 @@ const unsigned char *find_tlv_unchecked (const unsigned char *buffer, /* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE - on success. */ + on success. See also tlv_parse_tag. */ gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size, int *r_class, int *r_tag, int *r_constructed, @@ -129,7 +141,7 @@ void tlv_builder_add_end (tlv_builder_t tb); gpg_error_t tlv_builder_finalize (tlv_builder_t tb, void **r_obj, size_t *r_objlen); -/* Wite a TLV header to MEMBUF. */ +/* Write a TLV header to MEMBUF. */ void put_tlv_to_membuf (membuf_t *membuf, int class, int tag, int constructed, size_t length); @@ -137,6 +149,59 @@ void put_tlv_to_membuf (membuf_t *membuf, int class, int tag, size_t get_tlv_length (int class, int tag, int constructed, size_t length); +/*-- tlv-parser.c --*/ +#define TLV_PARSER_FLAG_T5793 1 /* Enable workaround for Mozilla bug. */ + +tlv_parser_t _tlv_parser_new (const unsigned char *buffer, size_t bufsize, + int verbosity, tlv_parser_t lasttlv, int lno); +tlv_parser_t _tlv_parser_release (tlv_parser_t tlv, int lno); + +void _tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv); +void _tlv_parser_dump_state (const char *text, const char *text2, + int lno, tlv_parser_t tlv); + +gpg_error_t _tlv_parser_next (tlv_parser_t tlv, unsigned int flags, int lno); + +unsigned int tlv_parser_level (tlv_parser_t tlv); +size_t tlv_parser_offset (tlv_parser_t tlv); +const char *tlv_parser_lastfunc (tlv_parser_t tlv); +const char *tlv_parser_lasterrstr (tlv_parser_t tlv); +void tlv_parser_set_pending (tlv_parser_t tlv); +size_t tlv_parser_tag_length (tlv_parser_t tlv, int with_header); + +void tlv_parser_skip (tlv_parser_t tlv); + +gpg_error_t tlv_expect_sequence (tlv_parser_t tlv); +gpg_error_t tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag); +gpg_error_t tlv_expect_set (tlv_parser_t tlv); +gpg_error_t tlv_expect_object (tlv_parser_t tlv, int class, int tag, + unsigned char const **r_data, + size_t *r_datalen); +gpg_error_t tlv_expect_octet_string (tlv_parser_t tlv, + unsigned char const **r_data, + size_t *r_datalen); +gpg_error_t tlv_expect_integer (tlv_parser_t tlv, int *r_value); +#ifdef GCRYPT_VERSION +gpg_error_t tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero, + gcry_mpi_t *r_value); +#endif +gpg_error_t tlv_expect_object_id (tlv_parser_t tlv, + unsigned char const **r_oid, + size_t *r_oidlen); +gpg_error_t tlv_expect_null (tlv_parser_t tlv); + +/* Easier to use wrapper around parse_ber_header. */ +gpg_error_t tlv_parse_tag (unsigned char const **buffer, + size_t *size, struct tag_info *ti); + +/* Convenience macro and macros to include the line number. */ +#define tlv_parser_new(a,b,c,d) _tlv_parser_new ((a),(b),(c),(d), __LINE__) +#define tlv_parser_release(a) _tlv_parser_release ((a), __LINE__) +#define tlv_parser_dump_tag(a,b) _tlv_parser_dump_tag ((a),__LINE__,(b)) +#define tlv_parser_dump_state(a,b,c) \ + _tlv_parser_dump_state ((a),(b),__LINE__,(c)) +#define tlv_next(a) _tlv_parser_next ((a),0, __LINE__) +#define tlv_next_with_flag(a,b) _tlv_parser_next ((a),(b), __LINE__) #endif /* SCD_TLV_H */ diff --git a/common/util.h b/common/util.h index aa24e39e6..b13f4300d 100644 --- a/common/util.h +++ b/common/util.h @@ -39,6 +39,10 @@ * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ +#if GPGRT_VERSION_NUMBER < 0x013800 /* 1.56 */ +# define GPG_ERR_UNEXPECTED_PACKET 216 +#endif + #ifndef EXTERN_UNLESS_MAIN_MODULE # if !defined (INCLUDED_BY_MAIN_MODULE) @@ -144,34 +148,6 @@ ssize_t read_line (FILE *fp, size_t *max_length); -/*-- b64enc.c and b64dec.c --*/ -struct b64state -{ - unsigned int flags; - int idx; - int quad_count; - FILE *fp; - estream_t stream; - char *title; - unsigned char radbuf[4]; - u32 crc; - int stop_seen:1; - int invalid_encoding:1; - gpg_error_t lasterr; -}; - -gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); -gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, - const char *title); -gpg_error_t b64enc_write (struct b64state *state, - const void *buffer, size_t nbytes); -gpg_error_t b64enc_finish (struct b64state *state); - -gpg_error_t b64dec_start (struct b64state *state, const char *title); -gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes); -gpg_error_t b64dec_finish (struct b64state *state); - /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, @@ -219,6 +195,7 @@ char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid); const char *pubkey_algo_to_string (int algo); const char *hash_algo_to_string (int algo); const char *cipher_mode_to_string (int mode); +const char *get_ecc_curve_from_key (gcry_sexp_t key); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); @@ -250,9 +227,11 @@ int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); int openpgp_oid_is_cv448 (gcry_mpi_t a); int openpgp_oid_is_ed448 (gcry_mpi_t a); +enum gcry_kem_algos openpgp_oid_to_kem_algo (const char *oidname); const char *openpgp_curve_to_oid (const char *name, - unsigned int *r_nbits, int *r_algo); -const char *openpgp_oid_to_curve (const char *oid, int canon); + unsigned int *r_nbits, int *r_algo, + int selector); +const char *openpgp_oid_to_curve (const char *oid, int mode); const char *openpgp_oid_or_name_to_curve (const char *oidname, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, @@ -267,6 +246,7 @@ void gnupg_set_homedir (const char *newdir); void gnupg_maybe_make_homedir (const char *fname, int quiet); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); +const char *gnupg_registry_dir (void); const char *gnupg_daemon_rootdir (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); @@ -316,11 +296,55 @@ void gnupg_set_builddir (const char *newdir); void gnupg_rl_initialize (void); /*-- helpfile.c --*/ + +/* Bit flags for gnupg_get_template. */ +#define GET_TEMPLATE_CURRENT_LOCALE 1 /* Use only the current locale. */ +#define GET_TEMPLATE_SUBST_ENVVARS 2 /* Substitute environment variables. */ +#define GET_TEMPLATE_CRLF 4 /* Use CR+LF. */ + +char *gnupg_get_template (const char *domain, const char *key, + unsigned int flags, const char *override_locale); char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); +/*-- kem.c --*/ +gpg_error_t gnupg_ecc_kem_kdf (void *kek, size_t kek_len, + int hashalgo, const void *ecdh, size_t ecdh_len, + const void *ecc_ct, size_t ecc_ct_len, + const void *ecc_pk, size_t ecc_pk_len, + unsigned char *kdf_params, + size_t kdf_params_len); + +gpg_error_t gnupg_kem_combiner (void *kek, size_t kek_len, + const void *ecc_ss, size_t ecc_ss_len, + const void *ecc_ct, size_t ecc_ct_len, + const void *mlkem_ss, size_t mlkem_ss_len, + const void *mlkem_ct, size_t mlkem_ct_len, + const void *fixedinfo, size_t fixedinfo_len); + +/* ECC parameters for KEM encryption/decryption. */ +struct gnupg_ecc_params +{ + const char *curve; /* Canonical name of the curve. */ + size_t pubkey_len; /* Pubkey length in the SEXP representation. */ + size_t scalar_len; + size_t point_len; + int hash_algo; /* Hash algo when it's used for composite KEM. */ + int kem_algo; + int scalar_reverse; /* Byte-oder is reverse. */ + int may_have_prefix; /* Point representation may have prefix. */ +}; + +const struct gnupg_ecc_params *gnupg_get_ecc_params (const char *curve); + +/* Maximum buffer sizes required for ECC KEM. */ +#define ECC_SCALAR_LEN_MAX 66 +#define ECC_POINT_LEN_MAX (1+2*ECC_SCALAR_LEN_MAX) +#define ECC_HASH_LEN_MAX 64 + + /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own @@ -330,7 +354,7 @@ void setup_libgcrypt_logging (void); /* Print an out of core message and die. */ void xoutofcore (void); -/* Wrapper aroung gpgrt_reallocarray. Uses the gpgrt alloc function +/* Wrapper around gpgrt_reallocarray. Uses the gpgrt alloc function * which redirects to the Libgcrypt versions via * init_common_subsystems. Thus this can be used interchangeable with * the other alloc functions. */ @@ -360,8 +384,6 @@ char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); char *decode_c_string (const char *src); -int is_file_compressed (const byte *buf, unsigned int buflen); - int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); @@ -383,11 +405,15 @@ struct compatibility_flags_s int parse_compatibility_flags (const char *string, unsigned int *flagvar, const struct compatibility_flags_s *flags); +gpg_error_t b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen); + + /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts - which an extisting but broken ttyname. */ + with an existing but broken ttyname. */ #if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME) # define gnupg_ttyname(n) _gnupg_ttyname ((n)) /* Systems without ttyname (W32) will merely return NULL. */ diff --git a/common/w32info-rc.h.in b/common/w32info-rc.h.in index 1e76b58a9..bec152eb2 100644 --- a/common/w32info-rc.h.in +++ b/common/w32info-rc.h.in @@ -29,4 +29,4 @@ built on @BUILD_HOSTNAME@ at @BUILD_TIMESTAMP@\0" #define W32INFO_PRODUCTVERSION "@VERSION@\0" #define W32INFO_LEGALCOPYRIGHT "Copyright \xa9 \ -2023 g10 Code GmbH\0" +2024 g10 Code GmbH\0" diff --git a/configure.ac b/configure.ac index 39cf5cbe0..b5a1766a8 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998-2019 Werner Koch # Copyright (C) 1998-2021 Free Software Foundation, Inc. -# Copyright (C) 2003-2023 g10 Code GmbH +# Copyright (C) 2003-2025 g10 Code GmbH # # This file is part of GnuPG. # @@ -29,7 +29,7 @@ min_automake_version="1.16.3" m4_define([mym4_package],[gnupg]) m4_define([mym4_major], [2]) m4_define([mym4_minor], [5]) -m4_define([mym4_micro], [0]) +m4_define([mym4_micro], [9]) # To start a new development series, i.e a new major or minor number # you need to mark an arbitrary commit before the first beta release @@ -47,7 +47,9 @@ m4_define([mym4_isbeta], m4_argn(2, mym4_verslist)) m4_define([mym4_version], m4_argn(4, mym4_verslist)) m4_define([mym4_revision], m4_argn(7, mym4_verslist)) m4_define([mym4_revision_dec], m4_argn(8, mym4_verslist)) +m4_define([mym4_commitid], m4_argn(9, mym4_verslist)) m4_esyscmd([echo ]mym4_version[>VERSION]) +m4_esyscmd([echo ]mym4_commitid[>>VERSION]) AC_INIT([mym4_package],[mym4_version],[https://bugs.gnupg.org]) # When changing the SWDB tag please also adjust the hard coded tags in @@ -55,25 +57,25 @@ AC_INIT([mym4_package],[mym4_version],[https://bugs.gnupg.org]) # As well as the source info for the man pages. AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg26", [swdb tag for this branch]) -NEED_GPGRT_VERSION=1.46 +NEED_GPGRT_VERSION=1.51 NEED_LIBGCRYPT_API=1 -NEED_LIBGCRYPT_VERSION=1.9.1 +NEED_LIBGCRYPT_VERSION=1.11.0 -NEED_LIBASSUAN_API=2 -NEED_LIBASSUAN_VERSION=2.5.0 +NEED_LIBASSUAN_API=3 +NEED_LIBASSUAN_VERSION=3.0.0 NEED_KSBA_API=1 NEED_KSBA_VERSION=1.6.3 NEED_NTBTLS_API=1 -NEED_NTBTLS_VERSION=0.1.0 +NEED_NTBTLS_VERSION=0.2.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 -NEED_GNUTLS_VERSION=3.0 +NEED_GNUTLS_VERSION=3.2 NEED_SQLITE_VERSION=3.27 @@ -231,18 +233,6 @@ test -n "$GNUPG_DIRMNGR_LDAP_PGM" \ && show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM" -# -# For a long time gpg 2.x was installed as gpg2. This changed with -# 2.2. This option can be used to install gpg under the name gpg2. -# -AC_ARG_ENABLE(gpg-is-gpg2, - AS_HELP_STRING([--enable-gpg-is-gpg2],[Set installed name of gpg to gpg2]), - gpg_is_gpg2=$enableval) -if test "$gpg_is_gpg2" = "yes"; then - AC_DEFINE(USE_GPG2_HACK, 1, [Define to install gpg as gpg2]) -fi -AM_CONDITIONAL(USE_GPG2_HACK, test "$gpg_is_gpg2" = "yes") - # SELinux support includes tracking of sensitive files to avoid # leaking their contents through processing these files by gpg itself @@ -525,7 +515,7 @@ AH_BOTTOM([ #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" #define GNUPG_CACHE_DIR "cache.d" -#define GNUPG_DEF_COPYRIGHT_LINE "Copyright (C) 2023 g10 Code GmbH" +#define GNUPG_DEF_COPYRIGHT_LINE "Copyright (C) 2025 g10 Code GmbH" /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that @@ -554,17 +544,17 @@ AH_BOTTOM([ #endif -/* We didn't define endianness above, so get it from OS macros. This - is intended for making fat binary builds on OS X. */ -#if !defined(BIG_ENDIAN_HOST) && !defined(LITTLE_ENDIAN_HOST) -#if defined(__BIG_ENDIAN__) -#define BIG_ENDIAN_HOST 1 -#elif defined(__LITTLE_ENDIAN__) -#define LITTLE_ENDIAN_HOST 1 -#else -#error "No endianness found" -#endif -#endif +/* If the configure check for endianness has been disabled, get it from + OS macros. This is intended for making fat binary builds on OS X. */ +#ifdef DISABLED_ENDIAN_CHECK +# if defined(__BIG_ENDIAN__) +# define WORDS_BIGENDIAN 1 +# elif defined(__LITTLE_ENDIAN__) +# undef WORDS_BIGENDIAN +# else +# error "No endianness found" +# endif +#endif /*DISABLED_ENDIAN_CHECK*/ /* Hack used for W32: ldap.m4 also tests for the ASCII version of @@ -603,9 +593,6 @@ AH_BOTTOM([ /* Under Windows we use the gettext code from libgpg-error. */ #define GPG_ERR_ENABLE_GETTEXT_MACROS -/* Under WindowsCE we use the strerror replacement from libgpg-error. */ -#define GPG_ERR_ENABLE_ERRNO_MACROS - #endif /*GNUPG_CONFIG_H_INCLUDED*/ ]) @@ -637,9 +624,7 @@ AC_PROG_RANLIB AC_CHECK_TOOL(AR, ar, :) AC_PATH_PROG(PERL,"perl") AC_CHECK_TOOL(WINDRES, windres, :) -AC_PATH_PROG(YAT2M, "yat2m", "./yat2m" ) -AC_ARG_VAR(YAT2M, [tool to convert texi to man pages]) -AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M") +AC_PATH_PROG(YAT2M, [yat2m], [:]) AC_SEARCH_LIBS([strerror],[cposix]) AC_SYS_LARGEFILE @@ -1344,10 +1329,19 @@ AC_MSG_NOTICE([checking for system characteristics]) AC_C_CONST AC_C_INLINE AC_C_VOLATILE +AC_ARG_ENABLE(endian-check, + AS_HELP_STRING([--disable-endian-check], + [disable the endian check and trust the OS provided macros]), + endiancheck=$enableval,endiancheck=yes) +if test x"$endiancheck" = xyes ; then + AC_C_BIGENDIAN +else + AC_DEFINE(DISABLED_ENDIAN_CHECK,1,[configure did not test for endianness]) +fi AC_TYPE_SIZE_T AC_TYPE_MODE_T AC_CHECK_FUNCS([sigdescr_np]) -AC_CHECK_DECLS([sys_siglist],[],[],[#include +AC_CHECK_DECLS([sys_siglist, _sys_siglist],[],[],[#include /* NetBSD declares sys_siglist in unistd.h. */ #ifdef HAVE_UNISTD_H # include @@ -1359,15 +1353,6 @@ gl_TYPE_SOCKLEN_T AC_SEARCH_LIBS([inet_addr], [nsl]) -AC_ARG_ENABLE(endian-check, - AS_HELP_STRING([--disable-endian-check], - [disable the endian check and trust the OS provided macros]), - endiancheck=$enableval,endiancheck=yes) - -if test x"$endiancheck" = xyes ; then - GNUPG_CHECK_ENDIAN -fi - # fixme: we should get rid of the byte type AC_CHECK_TYPES([byte, ushort, ulong, u16, u32]) AC_CHECK_SIZEOF(unsigned short) @@ -1385,6 +1370,8 @@ AC_CHECK_SIZEOF(time_t,,[[ ]]) GNUPG_TIME_T_UNSIGNED +# Check SOCKET type for Windows. +AC_CHECK_TYPES([SOCKET], [], [], [[#include "winsock2.h"]]) if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ @@ -1599,7 +1586,7 @@ if test "$build_tpm2d" = "yes"; then # until version 2.4.0. # # Note: the missing API is fairly serious and is also easily backportable - # so keep the check below as is intead of going by library version number. + # so keep the check below as is instead of going by library version number. ## AC_CHECK_LIB(tss2-esys, Esys_TR_GetTpmHandle, [], [ AC_MSG_WARN([Need Esys_TR_GetTpmHandle API (usually requires Intel TSS 2.4.0 or later, disabling TPM support)]) @@ -1613,10 +1600,9 @@ if test "$build_tpm2d" = "yes"; then if test "$have_libtss" != no; then AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) # look for a TPM emulator for testing - AC_PATH_PROG(TPMSERVER, tpm_server,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(SWTPM, swtpm,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(SWTPM_IOCTL, swtpm_ioctl,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) - AC_PATH_PROG(TSSSTARTUP, tssstartup,,/bin:/usr/bin:/usr/lib/ibmtss:/usr/libexec/ibmtss) + AC_PATH_PROG(TPMSERVER, tpm_server) + AC_PATH_PROG(TSSSTARTUP, tssstartup) + AC_PATH_PROG(SWTPM, swtpm) fi fi if test "$have_libtss" = no; then @@ -1625,7 +1611,7 @@ fi AC_SUBST(LIBTSS_LIBS) AC_SUBST(LIBTSS_CFLAGS) AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" != no) -AM_CONDITIONAL(TEST_LIBTSS, test -n "$TPMSERVER" || test -n "$SWTPM" && test -n "$TSSSTARTUP") +AM_CONDITIONAL(TEST_LIBTSS, test -n "$SWTPM" -o -n "$TPMSERVER" -a -n "$TSSSTARTUP") AC_SUBST(HAVE_LIBTSS) # @@ -1637,7 +1623,7 @@ if test "$GCC" = yes; then mycflags= mycflags_save=$CFLAGS - # Check whether gcc does not emit a diagnositc for unknown -Wno-* + # Check whether gcc does not emit a diagnostic for unknown -Wno-* # options. This is the case for gcc >= 4.6 AC_MSG_CHECKING([if gcc ignores unknown -Wno-* options]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @@ -1941,7 +1927,7 @@ AC_DEFINE_UNQUOTED(TPM2DAEMON_SOCK_NAME, "S.tpm2daemon", AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, - "hkps://keyserver.ubuntu.com", + "hkps://none", [The default keyserver for dirmngr to use, if none is explicitly given]) AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix]) @@ -1956,9 +1942,10 @@ fi # Provide information about the build. # BUILD_REVISION="mym4_revision" +BUILD_COMMITID="mym4_commitid" AC_SUBST(BUILD_REVISION) -AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", - [GIT commit id revision used to build this package]) +AC_DEFINE_UNQUOTED(BUILD_COMMITID, "$BUILD_COMMITID", + [Git commit id used to build this package]) changequote(,)dnl BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'` @@ -2102,6 +2089,14 @@ tests/tpm2dtests/Makefile tests/gpgme/Makefile tests/pkits/Makefile g10/gpg.w32-manifest +g10/gpgv.w32-manifest +sm/gpgsm.w32-manifest +kbx/keyboxd.w32-manifest +agent/gpg-agent.w32-manifest +scd/scdaemon.w32-manifest +dirmngr/dirmngr.w32-manifest +dirmngr/dirmngr_ldap.w32-manifest +dirmngr/dirmngr-client.w32-manifest tools/gpg-connect-agent.w32-manifest tools/gpgconf.w32-manifest tools/gpgtar.w32-manifest diff --git a/dirmngr/ChangeLog-2011 b/dirmngr/ChangeLog-2011 index 243f2b56f..30e026ff8 100644 --- a/dirmngr/ChangeLog-2011 +++ b/dirmngr/ChangeLog-2011 @@ -1373,7 +1373,7 @@ truncated search. * ldap.c (add_server_to_servers): Reactivated. (url_fetch_ldap): Call it here and try all configured servers in - case of a a failed lookup. + case of a failed lookup. (fetch_next_cert_ldap): Detect the truncation error flag. * misc.c (host_and_port_from_url, remove_percent_escapes): New. diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index feee2f5c8..b13f37d09 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -21,8 +21,15 @@ ## Process this file with automake to produce Makefile.in -EXTRA_DIST = OAUTHORS ONEWS ChangeLog-2011 tls-ca.pem -dist_pkgdata_DATA = sks-keyservers.netCA.pem +EXTRA_DIST = OAUTHORS ONEWS ChangeLog-2011 tls-ca.pem \ + dirmngr-w32info.rc dirmngr.w32-manifest.in \ + dirmngr_ldap-w32info.rc dirmngr_ldap.w32-manifest.in \ + dirmngr-client-w32info.rc dirmngr-client.w32-manifest.in + + + + +dist_pkgdata_DATA = bin_PROGRAMS = dirmngr dirmngr-client @@ -43,6 +50,16 @@ AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am +if HAVE_W32_SYSTEM +dirmngr_rc_objs = dirmngr-w32info.o +dirmngr_ldap_rc_objs = dirmngr_ldap-w32info.o +dirmngr_client_rc_objs = dirmngr-client-w32info.o + +dirmngr-w32info.o : dirmngr.w32-manifest ../common/w32info-rc.h +dirmngr_ldap-w32info.o : dirmngr_ldap.w32-manifest ../common/w32info-rc.h +dirmngr-client-w32info.o : dirmngr-client.w32-manifest ../common/w32info-rc.h +endif + AM_CFLAGS = $(USE_C99_CFLAGS) \ $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) \ $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(NTBTLS_CFLAGS) \ @@ -51,6 +68,7 @@ AM_CFLAGS = $(USE_C99_CFLAGS) \ if HAVE_W32_SYSTEM ldap_url = ldap-url.h ldap-url.c +NETLIBS += -lwinhttp -lsecurity else ldap_url = endif @@ -89,12 +107,13 @@ dirmngr_LDADD = $(libcommonpth) \ $(DNSLIBS) $(LIBASSUAN_LIBS) \ $(KSBA_LIBS) $(NPTH_LIBS) $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) \ - $(NETLIBS) + $(NETLIBS) $(dirmngr_rc_objs) if USE_LDAP dirmngr_LDADD += $(ldaplibs) $(LBER_LIBS) endif dirmngr_LDFLAGS = +dirmngr_DEPENDENCIES = $(dirmngr_rc_objs) if USE_LDAP dirmngr_ldap_SOURCES = dirmngr_ldap.c ldap-misc.c ldap-misc.h $(ldap_url) @@ -102,14 +121,18 @@ dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) $(LIBGCRYPT_CFLAGS) dirmngr_ldap_LDFLAGS = dirmngr_ldap_LDADD = $(libcommon) \ $(GPG_ERROR_LIBS) $(LIBGCRYPT_LIBS) $(LDAPLIBS) \ - $(LBER_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) + $(LBER_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) \ + $(dirmngr_ldap_rc_objs) +dirmngr_ldap_DEPENDENCIES = $(dirmngr_ldap_rc_objs) endif dirmngr_client_SOURCES = dirmngr-client.c dirmngr_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ - $(LIBGCRYPT_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV) + $(LIBGCRYPT_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV) \ + $(dirmngr_client_rc_objs) dirmngr_client_LDFLAGS = +dirmngr_client_DEPENDENCIES = $(dirmngr_client_rc_objs) t_common_src = t-support.h t-support.c diff --git a/dirmngr/ONEWS b/dirmngr/ONEWS index cb2050748..154d8e0bf 100644 --- a/dirmngr/ONEWS +++ b/dirmngr/ONEWS @@ -55,7 +55,7 @@ Noteworthy changes in version 1.0.1 (2007-08-16) Noteworthy changes in version 1.0.0 (2006-11-29) ------------------------------------------------ - * Bumbed the version number. + * Bumped the version number. * Removed included gettext. We now require the system to provide a suitable installation. @@ -174,7 +174,7 @@ Noteworthy changes in version 0.5.4 (2004-04-29) ------------------------------------------------ * New commands --ocsp-responder and --ocsp-signer to define a default - OCSP reponder if a certificate does not contain an assigned OCSP + OCSP responder if a certificate does not contain an assigned OCSP responder. diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c index 6b194f31c..07a47a525 100644 --- a/dirmngr/certcache.c +++ b/dirmngr/certcache.c @@ -100,7 +100,8 @@ static unsigned int any_cert_of_class; #ifdef HAVE_W32_SYSTEM -/* We load some functions dynamically. Provide typedefs for tehse +#include +/* We load some functions dynamically. Provide typedefs for these * functions. */ typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE) (HCRYPTPROV hProv, LPCSTR szSubsystemProtocol); @@ -224,7 +225,7 @@ cert_compute_fpr (ksba_cert_t cert, unsigned char *digest) -/* Cleanup one slot. This releases all resourses but keeps the actual +/* Cleanup one slot. This releases all resources but keeps the actual slot in the cache marked for reuse. */ static void clean_cache_slot (cert_item_t ci) diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index 9f0b910f3..824775483 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -2086,6 +2086,7 @@ crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl, err = validate_cert_chain (ctrl, crlissuer_cert, NULL, (VALIDATE_FLAG_TRUST_CONFIG + | VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_CRL | VALIDATE_FLAG_RECURSIVE), r_trust_anchor); @@ -2356,11 +2357,21 @@ crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical, NULL, NULL)); idx++) { + strlist_t sl; + if (!critical || !strcmp (oid, oidstr_authorityKeyIdentifier) || !strcmp (oid, oidstr_crlNumber) ) continue; + + for (sl=opt.ignored_crl_extensions; + sl && strcmp (sl->d, oid); sl = sl->next) + ; + if (sl) + continue; /* Is in ignored list. */ + log_error (_("unknown critical CRL extension %s\n"), oid); + log_info ("(CRL='%s')\n", url); if (!err2) err2 = gpg_error (GPG_ERR_INV_CRL); invalidate_crl |= INVCRL_UNKNOWN_EXTN; @@ -2634,6 +2645,11 @@ crl_cache_list (estream_t fp) crl_cache_entry_t entry; gpg_error_t err = 0; + for (entry = cache->entries; + entry && !entry->deleted; + entry = entry->next ) + es_fprintf (fp, "URL: %s\n", entry->url ); + for (entry = cache->entries; entry && !entry->deleted && !err; entry = entry->next ) diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index 5b6b648e2..620edf788 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -39,10 +39,10 @@ 2008) we need a context in the reader callback. */ struct reader_cb_context_s { - estream_t fp; /* The stream used with the ksba reader. */ - int checked:1; /* PEM/binary detection ahs been done. */ - int is_pem:1; /* The file stream is PEM encoded. */ - struct b64state b64state; /* The state used for Base64 decoding. */ + estream_t fp; /* The stream used with the ksba reader. */ + unsigned int checked:1; /* PEM/binary detection ahs been done. */ + unsigned int is_pem:1; /* The file stream is PEM encoded. */ + gpgrt_b64state_t b64state; /* The state used for Base64 decoding. */ }; @@ -126,14 +126,16 @@ my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) else { cb_ctx->is_pem = 1; - b64dec_start (&cb_ctx->b64state, ""); + cb_ctx->b64state = gpgrt_b64dec_start (""); + if (!cb_ctx->b64state) + return gpg_error_from_syserror (); } } if (cb_ctx->is_pem && *nread) { size_t nread2; - if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) + if (gpgrt_b64dec_proc (cb_ctx->b64state, buffer, *nread, &nread2)) { /* EOF from decoder. */ *nread = 0; @@ -581,7 +583,7 @@ crl_close_reader (ksba_reader_t reader) es_fclose (cb_ctx->fp); /* Release the base64 decoder state. */ if (cb_ctx->is_pem) - b64dec_finish (&cb_ctx->b64state); + gpgrt_b64dec_finish (cb_ctx->b64state); /* Release the callback context. */ xfree (cb_ctx); } diff --git a/dirmngr/dirmngr-client-w32info.rc b/dirmngr/dirmngr-client-w32info.rc new file mode 100644 index 000000000..020447bca --- /dev/null +++ b/dirmngr/dirmngr-client-w32info.rc @@ -0,0 +1,52 @@ +/* dirmngr-client-w32info.rc -*- c -*- + * Copyright (C) 2023 g10 Code GmbH + * + * 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 program 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. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s dirmngr client\0" + VALUE "InternalName", "dirmngr-client\0" + VALUE "OriginalFilename", "dirmngr-client.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "dirmngr-client.w32-manifest" diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c index 3912bf47b..ece4fbcc9 100644 --- a/dirmngr/dirmngr-client.c +++ b/dirmngr/dirmngr-client.c @@ -308,7 +308,7 @@ main (int argc, char **argv ) opt.dirmngr_program ? opt.dirmngr_program : gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR), - ! cmd_ping, + cmd_ping? 0 : ASSHELP_FLAG_AUTOSTART, opt.verbose, 0, NULL, NULL); @@ -441,11 +441,11 @@ static gpg_error_t data_cb (void *opaque, const void *buffer, size_t length) { gpg_error_t err; - struct b64state *state = opaque; + gpgrt_b64state_t state = opaque; if (buffer) { - err = b64enc_write (state, buffer, length); + err = gpgrt_b64enc_write (state, buffer, length); if (err) log_error (_("error writing base64 encoding: %s\n"), gpg_strerror (err)); @@ -853,14 +853,14 @@ do_lookup (assuan_context_t ctx, const char *pattern) gpg_error_t err; const unsigned char *s; char *line, *p; - struct b64state state; + gpgrt_b64state_t state; if (opt.verbose) log_info (_("looking up '%s'\n"), pattern); - err = b64enc_start (&state, stdout, NULL); - if (err) - return err; + state = gpgrt_b64enc_start (es_stdout, NULL); + if (!state) + return gpg_error_from_syserror (); line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); @@ -885,13 +885,13 @@ do_lookup (assuan_context_t ctx, const char *pattern) err = assuan_transact (ctx, line, - data_cb, &state, + data_cb, state, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); - err = b64enc_finish (&state); + err = gpgrt_b64enc_finish (state); xfree (line); return err; diff --git a/dirmngr/dirmngr-client.w32-manifest.in b/dirmngr/dirmngr-client.w32-manifest.in new file mode 100644 index 000000000..670bb60f1 --- /dev/null +++ b/dirmngr/dirmngr-client.w32-manifest.in @@ -0,0 +1,25 @@ + + +GNU Privacy Guard (Dirmngr Client) + + + + + + + + + + + + + + + + + + diff --git a/dirmngr/dirmngr-w32info.rc b/dirmngr/dirmngr-w32info.rc new file mode 100644 index 000000000..cc1475b8e --- /dev/null +++ b/dirmngr/dirmngr-w32info.rc @@ -0,0 +1,52 @@ +/* dirmngr-w32info.rc -*- c -*- + * Copyright (C) 2023 g10 Code GmbH + * + * 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 program 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. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s network access daemon\0" + VALUE "InternalName", "dirmngr\0" + VALUE "OriginalFilename", "dirmngr.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "dirmngr.w32-manifest" diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index bb54f4edd..08f313879 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -147,6 +147,7 @@ enum cmd_and_opt_values { oHTTPWrapperProgram, oIgnoreCert, oIgnoreCertExtension, + oIgnoreCRLExtension, oUseTor, oNoUseTor, oKeyServer, @@ -159,6 +160,7 @@ enum cmd_and_opt_values { oConnectQuickTimeout, oListenBacklog, oFakeCRL, + oCompatibilityFlags, aTest }; @@ -175,7 +177,7 @@ static gpgrt_opt_t opts[] = { 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", "@"), + ARGPARSE_c (aSupervised, "deprecated-supervised", "@"), #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")), @@ -219,10 +221,11 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), ARGPARSE_s_i (oMaxReplies, "max-replies", N_("|N|do not return more than N items in one query")), - ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ + ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oIgnoreCert,"ignore-cert", "@"), ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"), + ARGPARSE_s_s (oIgnoreCRLExtension,"ignore-crl-extension", "@"), ARGPARSE_header ("Network", N_("Network related options")), @@ -297,6 +300,7 @@ static gpgrt_opt_t opts[] = { ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */ ARGPARSE_s_n (oDebugCacheExpiredCerts, "debug-cache-expired-certs", "@"), + ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"), ARGPARSE_header (NULL, ""), /* Stop the header group. */ @@ -329,6 +333,14 @@ static struct debug_flags_s debug_flags [] = { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; +/* The list of compatibility flags. */ +static struct compatibility_flags_s compatibility_flags [] = + { + { COMPAT_RESTRICT_HTTP_REDIR, "restrict-http-redir" }, + { 0, NULL } + }; + + #define DEFAULT_MAX_REPLIES 10 #define DEFAULT_LDAP_TIMEOUT 15 /* seconds */ @@ -382,6 +394,9 @@ static enum } tor_mode; +/* Flag indicating that we are in supervised mode. */ +static int is_supervised; + /* Counter for the active connections. */ static int active_connections; @@ -438,9 +453,6 @@ static void handle_connections (assuan_fd_t listen_fd); static void gpgconf_versions (void); -/* NPth wrapper function definitions. */ -ASSUAN_SYSTEM_NPTH_IMPL; - static const char * my_strusage( int level ) { @@ -696,6 +708,7 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.ignored_certs = tmp; } FREE_STRLIST (opt.ignored_cert_extensions); + FREE_STRLIST (opt.ignored_crl_extensions); http_register_tls_ca (NULL); FREE_STRLIST (hkp_cacert_filenames); FREE_STRLIST (opt.keyserver); @@ -712,6 +725,7 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.debug_cache_expired_certs = 0; xfree (opt.fake_crl); opt.fake_crl = NULL; + opt.compat_flags = 0; return 1; } @@ -808,6 +822,10 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); break; + case oIgnoreCRLExtension: + add_to_strlist (&opt.ignored_crl_extensions, pargs->r.ret_str); + break; + case oUseTor: tor_mode = TOR_MODE_FORCE; break; @@ -879,6 +897,15 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.fake_crl = *pargs->r.ret_str? xstrdup (pargs->r.ret_str) : NULL; break; + case oCompatibilityFlags: + if (parse_compatibility_flags (pargs->r.ret_str, &opt.compat_flags, + compatibility_flags)) + { + pargs->r_opt = ARGPARSE_INVALID_ARG; + pargs->err = ARGPARSE_PRINT_WARNING; + } + break; + default: return 0; /* Not handled. */ } @@ -949,24 +976,48 @@ my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv) #endif +/* Helper for initialize_modules. */ static void thread_init (void) { - npth_init (); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); + static int npth_initialized = 0; + + if (!npth_initialized) + { + npth_initialized++; + npth_init (); + /* 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*/ + } 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*/ + /* 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); + assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL); } +static void +initialize_modules (void) +{ + thread_init (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + ks_hkp_init (); +} + + + int main (int argc, char **argv) { @@ -1078,12 +1129,12 @@ main (int argc, char **argv) socket_name = dirmngr_socket_name (); - /* The configuraton directories for use by gpgrt_argparser. */ + /* The configuration directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); /* We are re-using the struct, thus the reset flag. We OR the - * flags so that the internal intialized flag won't be cleared. */ + * flags so that the internal initialized flag won't be cleared. */ argc = orig_argc; argv = orig_argv; pargs.argc = &argc; @@ -1149,7 +1200,12 @@ main (int argc, char **argv) case oLDAPAddServers: opt.add_new_ldapservers = 1; break; case oFakedSystemTime: - gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); + { + 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 oForce: opt.force = 1; break; @@ -1286,12 +1342,9 @@ main (int argc, char **argv) log_debug ("... okay\n"); } - - thread_init (); - cert_cache_init (hkp_cacert_filenames); - crl_cache_init (); - ks_hkp_init (); + initialize_modules (); http_register_netactivity_cb (netactivity_action); + start_command_handler (ASSUAN_INVALID_FD, 0); shutdown_reaper (); } @@ -1303,6 +1356,8 @@ main (int argc, char **argv) if (!opt.quiet) log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised"); + is_supervised = 1; + /* In supervised mode, we expect file descriptor 3 to be an already opened, listening socket. @@ -1327,10 +1382,7 @@ main (int argc, char **argv) else log_set_prefix (NULL, 0); - thread_init (); - cert_cache_init (hkp_cacert_filenames); - crl_cache_init (); - ks_hkp_init (); + initialize_modules (); http_register_netactivity_cb (netactivity_action); handle_connections (3); shutdown_reaper (); @@ -1426,6 +1478,7 @@ main (int argc, char **argv) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { + log_libassuan_system_error (fd); log_error (_("error binding socket to '%s': %s\n"), serv_addr.sun_path, gpg_strerror (gpg_error_from_syserror ())); @@ -1553,11 +1606,9 @@ main (int argc, char **argv) } } - thread_init (); - cert_cache_init (hkp_cacert_filenames); - crl_cache_init (); - ks_hkp_init (); + initialize_modules (); http_register_netactivity_cb (netactivity_action); + handle_connections (fd); shutdown_reaper (); } @@ -1576,10 +1627,8 @@ main (int argc, char **argv) memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); - thread_init (); - cert_cache_init (hkp_cacert_filenames); - crl_cache_init (); - ks_hkp_init (); + initialize_modules (); + if (!argc) rc = crl_cache_load (&ctrlbuf, NULL); else @@ -1600,10 +1649,8 @@ main (int argc, char **argv) memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); - thread_init (); - cert_cache_init (hkp_cacert_filenames); - crl_cache_init (); - ks_hkp_init (); + initialize_modules (); + rc = crl_fetch (&ctrlbuf, argv[0], &reader); if (rc) log_error (_("fetching CRL from '%s' failed: %s\n"), @@ -1715,7 +1762,7 @@ dirmngr_deinit_default_ctrl (ctrl_t ctrl) 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: + assumed to be colon separated with these fields: 1. field: Hostname 2. field: Portnumber @@ -2013,6 +2060,7 @@ dirmngr_sighup_action (void) crl_cache_deinit (); cert_cache_init (hkp_cacert_filenames); crl_cache_init (); + http_reinitialize (); reload_dns_stuff (0); ks_hkp_reload (); } @@ -2200,7 +2248,7 @@ 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)); + FD_DBG (fd), strerror (errno)); assuan_sock_close (fd); return -1; } @@ -2234,7 +2282,7 @@ start_connection_thread (void *arg) active_connections++; if (opt.verbose) - log_info (_("handler for fd %d started\n"), FD2INT (fd)); + log_info (_("handler for fd %d started\n"), FD_DBG (fd)); session_id = ++last_session_id; if (!session_id) @@ -2242,7 +2290,7 @@ start_connection_thread (void *arg) start_command_handler (fd, session_id); if (opt.verbose) - log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); + log_info (_("handler for fd %d terminated\n"), FD_DBG (fd)); active_connections--; workqueue_run_post_session_tasks (session_id); @@ -2345,7 +2393,7 @@ handle_connections (assuan_fd_t listen_fd) to full second. */ FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); - nfd = FD2INT (listen_fd); + nfd = FD2NUM (listen_fd); if (my_inotify_fd != -1) { FD_SET (my_inotify_fd, &fdset); @@ -2362,7 +2410,7 @@ handle_connections (assuan_fd_t listen_fd) /* Shutdown test. */ if (shutdown_pending) { - if (!active_connections) + if (!active_connections || is_supervised) break; /* ready */ /* Do not accept new connections but keep on running the @@ -2446,11 +2494,13 @@ handle_connections (assuan_fd_t listen_fd) gnupg_fd_t fd; plen = sizeof paddr; - fd = INT2FD (npth_accept (FD2INT(listen_fd), - (struct sockaddr *)&paddr, &plen)); + fd = assuan_sock_accept (listen_fd, + (struct sockaddr *)&paddr, &plen); if (fd == GNUPG_INVALID_FD) { - log_error ("accept failed: %s\n", strerror (errno)); + gpg_error_t myerr = gpg_error_from_syserror (); + log_libassuan_system_error (listen_fd); + log_error ("accept failed: %s\n", gpg_strerror (myerr)); } else { @@ -2461,7 +2511,7 @@ handle_connections (assuan_fd_t listen_fd) memset (&argval, 0, sizeof argval); argval.afd = fd; snprintf (threadname, sizeof threadname, - "conn fd=%d", FD2INT(fd)); + "conn fd=%d", FD_DBG (fd)); ret = npth_create (&thread, &tattr, start_connection_thread, argval.aptr); @@ -2534,7 +2584,10 @@ gpgconf_versions (void) const char *s; int n; - /* Unfortunately Npth has no way to get the version. */ +#if NPTH_VERSION_NUMBER >= 0x010800 + es_fprintf (es_stdout, "* nPth %s (%s)\n\n", + npth_get_version (NULL), npth_get_version ("\x01\x02")); +#endif /*NPTH_VERSION_NUMBER*/ s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n); es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n", diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 1128e118b..ace40e6d5 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -132,6 +132,11 @@ struct OID per string. */ strlist_t ignored_cert_extensions; + /* A list of CRL extension OIDs which are ignored so that one can + * claim that a critical extension has been handled. One OID per + * string. */ + strlist_t ignored_crl_extensions; + /* Allow expired certificates in the cache. */ int debug_cache_expired_certs; @@ -154,6 +159,9 @@ struct current after nextUpdate. */ strlist_t keyserver; /* List of default keyservers. */ + + /* Compatibility flags (COMPAT_FLAG_xxxx). */ + unsigned int compat_flags; } opt; @@ -182,6 +190,18 @@ struct #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) #define DBG_KEEPTMP (opt.debug & DBG_KEEPTMP_VALUE) +/* Compatibility flags */ + +/* Since version 2.2.12 dirmngr restricted HTTP redirection in an + * attempt to mitigate certain CSRF attacks. It turned out that this + * breaks too many WKD deployments and that the attack scenario is not + * due to gnupg's redirecting but due to insecure configured systems. + * Thus from 2.4.3 on we disable this restriction but allow to use the + * old behaviour by using this compatibility flag. For details see + * https://dev.gnupg.org/T6477. */ +#define COMPAT_RESTRICT_HTTP_REDIR 1 + + /* A simple list of certificate references. FIXME: Better use certlist_t also for references (Store NULL at .cert) */ struct cert_ref_s @@ -221,7 +241,7 @@ struct server_control_s int audit_events; /* Send audit events to client. */ char *http_proxy; /* The used http_proxy or NULL. */ - nvc_t rootdse; /* Container wit the rootDSE properties. */ + nvc_t rootdse; /* Container with the rootDSE properties. */ unsigned int timeout; /* Timeout for connect calls in ms. */ diff --git a/dirmngr/dirmngr.w32-manifest.in b/dirmngr/dirmngr.w32-manifest.in new file mode 100644 index 000000000..115548b5c --- /dev/null +++ b/dirmngr/dirmngr.w32-manifest.in @@ -0,0 +1,25 @@ + + +GNU Privacy Guard (Archive tool) + + + + + + + + + + + + + + + + + + diff --git a/dirmngr/dirmngr_ldap-w32info.rc b/dirmngr/dirmngr_ldap-w32info.rc new file mode 100644 index 000000000..779d85837 --- /dev/null +++ b/dirmngr/dirmngr_ldap-w32info.rc @@ -0,0 +1,52 @@ +/* dirmngr_ldap-w32info.rc -*- c -*- + * Copyright (C) 2023 g10 Code GmbH + * + * 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 program 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. + */ + +#include "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s LDAP helper\0" + VALUE "InternalName", "dirmngr_ldap\0" + VALUE "OriginalFilename", "dirmngr_ldap.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "dirmngr_ldap.w32-manifest" diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c index 412d0ad1f..5fc260d74 100644 --- a/dirmngr/dirmngr_ldap.c +++ b/dirmngr/dirmngr_ldap.c @@ -107,7 +107,7 @@ static gpgrt_opt_t opts[] = { " a record oriented format"}, { oProxy, "proxy", 2, "|NAME|ignore host part and connect through NAME"}, - { oStartTLS, "starttls", 0, "use STARTLS for the conenction"}, + { oStartTLS, "starttls", 0, "use STARTTLS for the connection"}, { oLdapTLS, "ldaptls", 0, "use a TLS for the connection"}, { oNtds, "ntds", 0, "authenticate using AD"}, { oARecOnly, "areconly", 0, "do only an A record lookup"}, diff --git a/dirmngr/dirmngr_ldap.w32-manifest.in b/dirmngr/dirmngr_ldap.w32-manifest.in new file mode 100644 index 000000000..509b5e0d1 --- /dev/null +++ b/dirmngr/dirmngr_ldap.w32-manifest.in @@ -0,0 +1,25 @@ + + +GNU Privacy Guard (LDAP Helper) + + + + + + + + + + + + + + + + + + diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c index 0edbc0442..270717215 100644 --- a/dirmngr/dns-stuff.c +++ b/dirmngr/dns-stuff.c @@ -34,6 +34,7 @@ # define WIN32_LEAN_AND_MEAN # ifdef HAVE_WINSOCK2_H # include +# include # endif # include # include diff --git a/dirmngr/dns.c b/dirmngr/dns.c index e0eb33244..5c7bb08d8 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -7749,18 +7749,18 @@ 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 __linux + /* Linux returns EINVAL when address was once bound to + localhost and the socket is reused for an 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 reserved(?) asynchronously. */ - goto udp_connect_retry; + } +#endif if (error) goto error; @@ -9761,7 +9761,7 @@ struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_ty /* * 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), + * AF_INET6 not in intersection of .ai_family and resconf.family), * then what? */ switch (ai->qtype) { diff --git a/dirmngr/http-common.h b/dirmngr/http-common.h index 5e6657b16..ddb340de6 100644 --- a/dirmngr/http-common.h +++ b/dirmngr/http-common.h @@ -22,4 +22,6 @@ const char *get_default_keyserver (int name_only); +void http_reinitialize (void); + #endif /* HTTP_COMMON_H */ diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c index 2191acb60..c251f57ef 100644 --- a/dirmngr/http-ntbtls.c +++ b/dirmngr/http-ntbtls.c @@ -78,7 +78,7 @@ gnupg_http_tls_verify_cb (void *opaque, validate_flags = VALIDATE_FLAG_TLS; /* If we are using the standard hkps:// pool use the dedicated root - * certificate. Note that this differes from the GnuTLS + * certificate. Note that this differs from the GnuTLS * implementation which uses this special certificate only if no * other certificates are configured. */ /* Disabled for 2.3.2 to due problems with the standard hkps pool. */ diff --git a/dirmngr/http.c b/dirmngr/http.c index b4c501736..5723a13f2 100644 --- a/dirmngr/http.c +++ b/dirmngr/http.c @@ -2,7 +2,7 @@ * Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, 2011, 2014 Werner Koch - * Copyright (C) 2015-2017, 2021 g10 Code GmbH + * Copyright (C) 2015-2017, 2021, 2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -64,12 +64,9 @@ # include # endif # include -# ifndef EHOSTUNREACH -# define EHOSTUNREACH WSAEHOSTUNREACH -# endif -# ifndef EAFNOSUPPORT -# define EAFNOSUPPORT WSAEAFNOSUPPORT -# endif +# include +# define SECURITY_WIN32 1 +# include #else /*!HAVE_W32_SYSTEM*/ # include # include @@ -210,10 +207,29 @@ struct cookie_s /* True if TLS is to be used. */ int use_tls; + /* Optional malloced buffer holding pending bytes for the read + * function. LEN gives the used length, SIZE the allocated length. + * Used by the up_to_empty_line machinery. */ + struct { + size_t size; + size_t len; + char *data; + } pending; + /* The remaining content length and a flag telling whether to use the content length. */ uint64_t content_length; unsigned int content_length_valid:1; + + /* If the next flag is set the read function will limit the returned + * buffer to an empty line. That is the the pattern "\n\r\n" is + * detected and any further bytes are not returned to the caller. + * The flag is then reset. For technical reason we might have + * already read more which will be then saved for the next call in + * the PENDING buffer. */ + unsigned int up_to_empty_line:1; + unsigned int last_was_lf:1; /* Helper to detect empty line. */ + unsigned int last_was_lfcr:1; /* Helper to detect empty line. */ }; typedef struct cookie_s *cookie_t; @@ -230,6 +246,33 @@ static es_cookie_io_functions_t simple_cookie_functions = }; #endif +enum auth_negotiate_states + { + AUTH_NGT_NONE = 0, + AUTH_NGT_RCVD = 1, + AUTH_NGT_SENT = 2 + }; + +/* An object to store information about a proxy. */ +struct proxy_info_s +{ + parsed_uri_t uri; /* The parsed proxy URL. */ + int is_http_proxy; /* This is an http proxy. */ + +#ifdef HAVE_W32_SYSTEM + CredHandle cred_handle; /* Credential handle. */ + wchar_t *spn; /* Service principal name. */ + CtxtHandle ctxt_handle; /* Security context. */ + unsigned long token_size; /* Max. length of a token. */ + unsigned int cred_handle_valid:1; + unsigned int ctxt_handle_valid:1; +#endif /*HAVE_W32_SYSTEM*/ + + unsigned char *outtoken; /* The output token allocated with token_size. */ + unsigned long outtoklen; /* The current length of the token. */ +}; +typedef struct proxy_info_s *proxy_info_t; + #if SIZEOF_UNSIGNED_LONG == 8 # define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */ @@ -252,7 +295,7 @@ struct http_session_s } verify; char *servername; /* Malloced server name. */ - /* A callback function to log details of TLS certifciates. */ + /* A callback function to log details of TLS certificates. */ void (*cert_log_cb) (http_session_t, gpg_error_t, const char *, const void **, size_t *); @@ -297,6 +340,7 @@ struct http_context_s my_socket_t sock; unsigned int in_data:1; unsigned int is_http_0_9:1; + unsigned int keep_alive:1; /* Keep the connection alive. */ estream_t fp_read; estream_t fp_write; void *write_cookie; @@ -317,13 +361,13 @@ struct http_context_s static int opt_verbose; static int opt_debug; -/* The global callback for the verification function. */ +/* The global callback for the verification function for GNUTLS. */ static gpg_error_t (*tls_callback) (http_t, http_session_t, int); -/* The list of files with trusted CA certificates. */ +/* The list of files with trusted CA certificates for GNUTLS. */ static strlist_t tls_ca_certlist; -/* The list of files with extra trusted CA certificates. */ +/* The list of files with extra trusted CA certificates for GNUTLS. */ static strlist_t cfg_ca_certlist; /* The global callback for net activity. */ @@ -397,7 +441,7 @@ _my_socket_new (int lnr, assuan_fd_t 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); + lnr, so, FD_DBG (so->fd)); return so; } #define my_socket_new(a) _my_socket_new (__LINE__, (a)) @@ -409,7 +453,7 @@ _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); + lnr, so, FD_DBG (so->fd), so->refcount); return so; } #define my_socket_ref(a) _my_socket_ref (__LINE__,(a)) @@ -427,7 +471,7 @@ _my_socket_unref (int lnr, my_socket_t 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); + lnr, so, FD_DBG (so->fd), so->refcount); if (!so->refcount) { @@ -596,7 +640,7 @@ http_set_verbose (int verbose, int debug) /* Register a non-standard global TLS callback function. If no verification is desired a callback needs to be registered which - always returns NULL. */ + always returns NULL. Only used for GNUTLS. */ void http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int)) { @@ -607,7 +651,7 @@ http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int)) /* 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. */ + removed. Only used for GNUTLS. */ void http_register_tls_ca (const char *fname) { @@ -636,7 +680,8 @@ http_register_tls_ca (const char *fname) * 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. */ + * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. + * Only used for GNUTLS. */ void http_register_cfg_ca (const char *fname) { @@ -737,6 +782,64 @@ http_session_release (http_session_t sess) } +/* Create a write stream and store it in the fp_write member. Also + * store the tls flag and the session. */ +static gpg_error_t +make_fp_write (http_t hd, int use_tls, http_session_t session) +{ + cookie_t cookie; + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->sock = my_socket_ref (hd->sock); + cookie->use_tls = use_tls; + if (session) + cookie->session = http_session_ref (session); + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + gpg_error_t err = gpg_error_from_syserror (); + my_socket_unref (cookie->sock, NULL, NULL); + if (session) + http_session_unref (cookie->session); + xfree (cookie); + return err; + } + hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */ + return 0; +} + + +/* Create a read stream and store it in the fp_read member. Also + * store the tls flag and the session. */ +static gpg_error_t +make_fp_read (http_t hd, int use_tls, http_session_t session) +{ + cookie_t cookie; + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->sock = my_socket_ref (hd->sock); + cookie->use_tls = use_tls; + if (session) + cookie->session = http_session_ref (session); + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + gpg_error_t err = gpg_error_from_syserror (); + my_socket_unref (cookie->sock, NULL, NULL); + if (session) + http_session_unref (cookie->session); + xfree (cookie); + return err; + } + hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */ + return 0; +} + + /* 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: @@ -786,6 +889,8 @@ http_session_new (http_session_t *r_session, int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS); int is_hkps_pool; + (void)intended_hostname; + rc = gnutls_certificate_allocate_credentials (&sess->certcred); if (rc < 0) { @@ -850,7 +955,6 @@ http_session_new (http_session_t *r_session, /* 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); @@ -861,7 +965,6 @@ http_session_new (http_session_t *r_session, shown = 1; log_info ("number of system provided CAs: %d\n", rc); } -#endif /* gnutls >= 3.0.20 */ } /* Add other configured certificates to the session. */ @@ -1027,7 +1130,6 @@ http_raw_connect (ctrl_t ctrl, http_t *r_hd, { gpg_error_t err = 0; http_t hd; - cookie_t cookie; *r_hd = NULL; @@ -1075,39 +1177,13 @@ http_raw_connect (ctrl_t ctrl, http_t *r_hd, } /* 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. */ + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; - 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. */ + err = make_fp_read (hd, 0, NULL); + if (err) + goto leave; /* Register close notification to interlock the use of es_fclose in http_close and in user code. */ @@ -1139,7 +1215,7 @@ 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:"); + log_debug ("http.c:request-header:start_data:\n"); es_fputs ("\r\n", hd->fp_write); es_fflush (hd->fp_write); hd->in_data = 1; @@ -1155,6 +1231,7 @@ http_wait_response (http_t hd) gpg_error_t err; cookie_t cookie; int use_tls; + int newfpread; /* Make sure that we are in the data. */ http_start_data (hd); @@ -1166,41 +1243,36 @@ http_wait_response (http_t hd) 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; + if (!hd->keep_alive) + { + 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)) + if (!hd->keep_alive && (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) + newfpread = 0; + if (!hd->keep_alive || !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 = make_fp_read (hd, use_tls, hd->session); + if (err) + return err; + newfpread = 1; + ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1; } err = parse_response (hd); - if (!err) + if (!err && newfpread) err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd); return err; @@ -1789,34 +1861,266 @@ is_hostname_port (const char *string) } -/* - * 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) +/* Free the PROXY object. */ +static void +release_proxy_info (proxy_info_t proxy) { - 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 (!proxy) + return; + http_release_parsed_uri (proxy->uri); + xfree (proxy->outtoken); +#ifdef HAVE_W32_SYSTEM + if (proxy->ctxt_handle_valid) + DeleteSecurityContext (&proxy->ctxt_handle); + if (proxy->cred_handle_valid) + FreeCredentialsHandle (&proxy->cred_handle); +#endif + xfree (proxy); +} + + +/* Return an http session object. If clear is set, the object is + * destroyed. On error nULL is returned. */ +#ifdef HAVE_W32_SYSTEM +static HINTERNET +w32_get_internet_session (int clear) +{ + static HINTERNET session; + + if (clear) + { + if (session) + { + WinHttpCloseHandle (session); + session = NULL; + } + return NULL; + } + + if (!session) + { + session = WinHttpOpen (L"GnuPG dirmngr", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session) + { + log_error ("WinHttpOpen failed: %s\n", w32_strerror (-1)); + return NULL; + } + } + + return session; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Return a proxy using a Windows API. */ +#ifdef HAVE_W32_SYSTEM +static char * +w32_get_proxy (const char *url) +{ + WINHTTP_AUTOPROXY_OPTIONS options = {0}; + WINHTTP_PROXY_INFO info; + char *result = NULL; + char *p; + wchar_t *wurl; + int defaultcfg = 0; + + wurl = utf8_to_wchar (url); + if (!wurl) + { + log_error ("utf8_to_wchar failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + return NULL; + } + + options.dwFlags = (WINHTTP_AUTOPROXY_ALLOW_AUTOCONFIG + | WINHTTP_AUTOPROXY_ALLOW_CM + | WINHTTP_AUTOPROXY_ALLOW_STATIC + | WINHTTP_AUTOPROXY_AUTO_DETECT + | WINHTTP_AUTOPROXY_SORT_RESULTS); + options.dwAutoDetectFlags = (WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A); + options.fAutoLogonIfChallenged = TRUE; + + if (opt_debug) + log_debug ("calling WinHttpGetProxyForUrl (%s)\n", url); + if (!WinHttpGetProxyForUrl (w32_get_internet_session (0), + wurl, &options, &info)) + { + int ec = (int)GetLastError (); + if (ec == ERROR_WINHTTP_AUTODETECTION_FAILED) + { + if (opt_debug) + log_debug ("calling WinHttpGetDefaultProxyConfiguration\n"); + if (!WinHttpGetDefaultProxyConfiguration (&info)) + { + if (opt_verbose) + log_info ("WinHttpGetDefaultProxyConfiguration failed: " + "%s (%d)\n", w32_strerror (ec), ec); + xfree (wurl); + return NULL; + } + defaultcfg = 1; + } + else + { + if (opt_verbose) + log_info ("WinHttpGetProxyForUrl failed: %s (%d)\n", + w32_strerror (ec), ec); + xfree (wurl); + return NULL; + } + } + xfree (wurl); + + if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY) + { + result = wchar_to_utf8 (info.lpszProxy); + if (!result) + log_error ("wchar_to_utf8 failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + else + { + if (opt_debug) + log_debug ("proxies to use: '%s'\n", result); + /* The returned proxies are delimited by whitespace or + * semicolons. We return only the first proxy. */ + for (p=result; *p; p++) + if (spacep (p) || *p == ';') + { + *p = 0; + break; + } + } + } + else if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY) + { + /* No proxy shall be used. */ + } + else + log_error ("%s returned unexpected code %lu\n", + defaultcfg? "WinHttpGetDefaultProxyConfiguration" + :"WinHttpGetProxyForUrl", info.dwAccessType); + + if (info.lpszProxy) + GlobalFree (info.lpszProxy); + if (info.lpszProxyBypass) + GlobalFree (info.lpszProxyBypass); + return result; +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Return the proxy to be used for the URL or host specified in HD. + * If OVERRIDE_PROXY is not NULL and not empty, this proxy will be + * used instead of any configured or dynamically determined proxy. If + * the function runs into an error an error code is returned and NULL + * is stored at R_PROXY. If the function was successful and a proxy + * is to be used, information on the procy is stored at R_PROXY; if no + * proxy shall be used R_PROXY is set to NULL. Caller should always + * use release_proxy_info on the value stored at R_PROXY. */ +static gpg_error_t +get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy) +{ + gpg_error_t err = 0; + const char *proxystr, *s; + proxy_info_t proxy; +#ifdef HAVE_W32_SYSTEM + char *proxystrbuf = NULL; +#endif + + *r_proxy = NULL; + + if (override_proxy && *override_proxy) + proxystr = override_proxy; + else if (!(hd->flags & HTTP_FLAG_TRY_PROXY)) + return 0; /* --honor-http-proxy not active */ + else if ((s = getenv (HTTP_PROXY_ENV)) && *s) + proxystr = s; +#ifdef HAVE_W32_SYSTEM + else if (hd->uri && hd->uri->original + && (proxystrbuf = w32_get_proxy (hd->uri->original))) + proxystr = proxystrbuf; +#endif + else + return 0; /* No proxy known. */ + + proxy = xtrycalloc (1, sizeof *proxy); + if (!proxy) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory for proxy\n"); + goto leave; + } + + err = parse_uri (&proxy->uri, proxystr, 0, 0); + if (gpg_err_code (err) == GPG_ERR_INV_URI + && is_hostname_port (proxystr)) + { + /* Retry assuming a "hostname:port" string. */ + char *tmpname = strconcat ("http://", proxystr, NULL); + if (!tmpname) + err = gpg_error_from_syserror (); + else if (!parse_uri (&proxy->uri, tmpname, 0, 0)) + err = 0; + xfree (tmpname); + } + + if (!err) + { + /* Get rid of the escapes in the authstring. */ + if (proxy->uri->auth) + remove_escapes (proxy->uri->auth); + + if (!strcmp (proxy->uri->scheme, "http")) + proxy->is_http_proxy = 1; + else if (!strcmp (proxy->uri->scheme, "socks4") + || !strcmp (proxy->uri->scheme, "socks5h")) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else + err = gpg_error (GPG_ERR_INV_URI); + + if (err) + { + log_error ("invalid HTTP proxy (%s): %s\n", + proxystr, gpg_strerror (err)); + err = gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION); + } + else if (opt_verbose) + log_info ("using '%s' to proxy '%s'\n", + proxystr, hd->uri? hd->uri->original : NULL); + } + + leave: +#ifdef HAVE_W32_SYSTEM + xfree (proxystrbuf); +#endif + if (err) + xfree (proxy); + else + *r_proxy = proxy; + return err; +} + + +/* Some checks done by send_request. */ +static gpg_error_t +send_request_basic_checks (http_t hd) +{ + int mode; 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); + return gpg_error (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); + return gpg_error (GPG_ERR_INTERNAL); } if (opt_debug) log_debug ("Using TLS library: %s %s\n", @@ -1827,37 +2131,35 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, #endif /*HTTP_USE_GNUTLS*/ ); - if ((hd->flags & HTTP_FLAG_FORCE_TOR)) + if ((hd->flags & HTTP_FLAG_FORCE_TOR) + && (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)) { - 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; + log_error ("Tor support is not available\n"); + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } - server = *hd->uri->host ? hd->uri->host : "localhost"; - port = hd->uri->port ? hd->uri->port : 80; + return 0; +} + + +/* Helper for send_request to set the servername. */ +static gpg_error_t +send_request_set_sni (http_t hd, const char *name) +{ + gpg_error_t err = 0; +# if HTTP_USE_GNUTLS + int rc; +# endif /* 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); + hd->session->servername = xtrystrdup (name); if (!hd->session->servername) { - err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); - return err; + err = gpg_error_from_syserror (); + goto leave; } #if HTTP_USE_NTBTLS @@ -1866,7 +2168,7 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, if (err) { log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err)); - return err; + goto leave; } #elif HTTP_USE_GNUTLS rc = gnutls_server_name_set (hd->session->tls_session, @@ -1878,181 +2180,27 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, #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; + leave: + return err; +} - 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; - } +/* Run the NTBTLS handshake if needed. */ #if HTTP_USE_NTBTLS +static gpg_error_t +run_ntbtls_handshake (http_t hd) +{ + gpg_error_t err; + estream_t in, out; + 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", + in = es_fopencookie (hd->sock->fd, "rb", simple_cookie_functions); # else in = es_fdopen_nc (hd->sock->fd, "rb"); @@ -2060,12 +2208,11 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, if (!in) { err = gpg_error_from_syserror (); - xfree (proxy_authstr); - return err; + goto leave; } # ifdef HAVE_W32_SYSTEM - out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb", + out = es_fopencookie (hd->sock->fd, "wb", simple_cookie_functions); # else out = es_fdopen_nc (hd->sock->fd, "wb"); @@ -2074,8 +2221,7 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { err = gpg_error_from_syserror (); es_fclose (in); - xfree (proxy_authstr); - return err; + goto leave; } err = ntbtls_set_transport (hd->session->tls_session, in, out); @@ -2083,8 +2229,9 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_info ("TLS set_transport failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); - xfree (proxy_authstr); - return err; + es_fclose (in); + es_fclose (out); + goto leave; } if (hd->session->verify_cb) @@ -2095,71 +2242,62 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_error ("ntbtls_set_verify_cb failed: %s\n", gpg_strerror (err)); - xfree (proxy_authstr); - return err; + goto leave; } } while ((err = ntbtls_handshake (hd->session->tls_session))) { -#if NTBTLS_VERSION_NUMBER >= 0x000200 unsigned int tlevel, ttype; - const char *s = ntbtls_get_last_alert (hd->session->tls_session, - &tlevel, &ttype); + const char *s; + + s = ntbtls_get_last_alert (hd->session->tls_session, &tlevel, &ttype); if (s) log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype); -#endif switch (err) { default: log_info ("TLS handshake failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); - xfree (proxy_authstr); - return err; + goto leave; } } 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. */ + /* Note that in contrast to GNUTLS NTBTLS uses a registered + * callback to run the verification as part of the handshake. */ + err = 0; + /* FIXME: We could check that the CB has been called and if not + * error out with this warning: + * if (err) + * { + * log_info ("TLS connection authentication failed: %s <%s>\n", + * gpg_strerror (err), gpg_strsource (err)); + * goto leave; + * } + */ + } + else + err = 0; - 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); + leave: + return err; +} +#endif /*HTTP_USE_NTBTLS*/ - 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 +/* Run the GNUTLS handshake if needed. */ +#if HTTP_USE_GNUTLS +static gpg_error_t +run_gnutls_handshake (http_t hd, const char *server) +{ + gpg_error_t err; + int rc; 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, @@ -2195,8 +2333,8 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, } else log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); - xfree (proxy_authstr); - return gpg_err_make (default_errsource, GPG_ERR_NETWORK); + err = gpg_error (GPG_ERR_NETWORK); + goto leave; } hd->session->verify.done = 0; @@ -2208,12 +2346,578 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, { log_info ("TLS connection authentication failed: %s\n", gpg_strerror (err)); - xfree (proxy_authstr); - return err; + goto leave; + } + } + else + err =0; + + leave: + return err; +} +#endif /*HTTP_USE_GNUTLS*/ + + +/* It INPUTSTRING is NULL get the initial token. If INPUTSTRING is not + * NULL, decode the string and use this as input from the server. On + * success the final output token is stored at PROXY->OUTTOKEN and + * OUTTOKLEN. IF the authentication succeeded OUTTOKLEN is zero. */ +static gpg_error_t +proxy_get_token (proxy_info_t proxy, const char *inputstring) +{ +#ifdef HAVE_W32_SYSTEM + gpg_error_t err; + int rc; + SecBuffer chlg_buf; /* challenge buffer */ + SecBufferDesc chlg_desc; /* challenge descriptor */ + SecBuffer resp_buf; /* response buffer */ + SecBufferDesc resp_desc; /* response descriptor */ + unsigned long attrs; + TimeStamp expiry; /* (value not used) */ + void *intoken = NULL; + size_t intoklen; + + if (inputstring) + { + /* The input is expected in the token parameter but the parameter + * name is often forgotten. Thus we simply detect the parameter + * name and skip it, assuming no other parameters are given. */ + if (!strncmp (inputstring, "token=", 6)) + inputstring += 6; + + err = b64decode (inputstring, NULL, &intoken, &intoklen); + /* Just to be safe that we don't overflow an ulong we check the + * actual size against an arbitrary limit. */ + if (!err && intoklen > 65535) + err = gpg_error (GPG_ERR_ERANGE); + if (err || !intoklen) + { + log_error ("error decoding received auth token: %s\n", + err? gpg_strerror (err):"empty challenge token received"); + if (!err) + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; } } -#endif /*HTTP_USE_GNUTLS*/ + if (!proxy->spn) + { + char *buffer = strconcat ("HTTP/", (*proxy->uri->host + ?proxy->uri->host:"localhost"), NULL); + if (!buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (opt_debug) + log_debug ("http.c:proxy_connect: using '%s' as SPN\n", buffer); + proxy->spn = utf8_to_wchar (buffer); + xfree (buffer); + if (!proxy->spn) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + if (!proxy->token_size || !proxy->outtoken) /* Not yet initialized. */ + { + PSecPkgInfoW pinfo; + + rc = QuerySecurityPackageInfoW (NEGOSSP_NAME_W, &pinfo); + if (rc) + { + log_error ("QSPI(Negotiate) failed: %s (%d)\n", + w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + proxy->token_size = pinfo->cbMaxToken; + FreeContextBuffer (pinfo); + + proxy->outtoken = xtrymalloc (proxy->token_size); + if (!proxy->outtoken) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + if (!proxy->cred_handle_valid) + { + rc = AcquireCredentialsHandleW (NULL, NEGOSSP_NAME_W, + SECPKG_CRED_OUTBOUND, NULL, + NULL, /* Current user */ + NULL, /* reserved */ + NULL, /* reserved */ + &proxy->cred_handle, + NULL /* expiry */); + if (rc) + { + log_error ("ACH(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + } + proxy->cred_handle_valid = 1; + } + + /* Now generate our challenge-response message. */ + if (intoken) + { + chlg_buf.BufferType = SECBUFFER_TOKEN; + chlg_buf.pvBuffer = intoken; + chlg_buf.cbBuffer = intoklen; + chlg_desc.ulVersion = SECBUFFER_VERSION; + chlg_desc.cBuffers = 1; + chlg_desc.pBuffers = &chlg_buf; + } + + resp_buf.BufferType = SECBUFFER_TOKEN; + resp_buf.pvBuffer = proxy->outtoken; + resp_buf.cbBuffer = proxy->token_size; + resp_desc.ulVersion = SECBUFFER_VERSION; + resp_desc.cBuffers = 1; + resp_desc.pBuffers = &resp_buf; + rc = InitializeSecurityContextW (&proxy->cred_handle, + (intoken && proxy->ctxt_handle_valid) + ? &proxy->ctxt_handle : NULL, + proxy->spn, /* service principal name */ + ISC_REQ_CONFIDENTIALITY, + 0, /* reserved */ + SECURITY_NATIVE_DREP, + intoken? &chlg_desc : NULL, + 0, /* reserved */ + &proxy->ctxt_handle, /* new context */ + &resp_desc, /* the output. */ + &attrs, /* attribs of the context. */ + &expiry); + switch (rc) + { + case SEC_E_OK: /* All done and no more ISC expected. */ + break; + + case SEC_I_COMPLETE_AND_CONTINUE: /* Need to call CompleteAuthToken. */ + case SEC_I_COMPLETE_NEEDED: + rc = CompleteAuthToken (&proxy->ctxt_handle, &resp_desc); + log_error ("CompleteAuthToken failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + break; + + case SEC_I_CONTINUE_NEEDED: /* Send the new token to the client. */ + break; + + default: + log_error ("ISC(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc); + err = gpg_error (GPG_ERR_NO_AUTH); + goto leave; + } + + proxy->outtoklen = resp_buf.cbBuffer; + proxy->ctxt_handle_valid = 1; + err = 0; + + leave: + xfree (intoken); + return err; + +#else /*!HAVE_W32_SYSTEM*/ + + (void)proxy; + (void)inputstring; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + +#endif /*!HAVE_W32_SYSTEM*/ +} + + +/* Use the CONNECT method to proxy our TLS stream. */ +static gpg_error_t +run_proxy_connect (http_t hd, proxy_info_t proxy, + const char *httphost, const char *server, + unsigned short port) +{ + gpg_error_t err; + int saved_flags = hd->flags; + char *authhdr = NULL; + char *request = NULL; + char *tmpstr = NULL; + const char *s, *parms; + unsigned int idx; + int auth_basic = 0; + enum auth_negotiate_states authstate = 0; + unsigned int authpasses = 0; + + /* Authentication methods implemented here: + * RFC-2617 - HTTP Authentication: Basic and Digest Access Authentication + * RFC-4559 - SPNEGO-based Kerberos and NTLM HTTP Authentication + */ + auth_basic = !!proxy->uri->auth; + hd->keep_alive = !auth_basic; /* We may need to send more requests. */ + + /* For basic authentication we need to send just one request. */ + if (auth_basic + && !(authhdr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + again: + xfree (request); + request = es_bsprintf ("CONNECT %s:%hu HTTP/1.%c\r\nHost: %s:%hu\r\n%s%s", + httphost ? httphost : server, + port, + auth_basic? '0' : '1', + httphost ? httphost : server, + port, + authhdr ? authhdr : "", + hd->keep_alive? "Connection: keep-alive\r\n" : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_string (request, "http.c:proxy:request:"); + + if (!hd->fp_write) + { + err = make_fp_write (hd, 0, NULL); + if (err) + goto leave; + } + + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Make sure http_wait_response doesn't close the stream. */ + saved_flags = hd->flags; + hd->flags &= ~HTTP_FLAG_SHUTDOWN; + + /* Get the response and set hd->fp_read */ + err = http_wait_response (hd); + if (err) + goto leave; + + /* Reset state. */ + es_clearerr (hd->fp_read); + ((cookie_t)(hd->read_cookie))->up_to_empty_line = 1; + hd->in_data = 0; + + if (hd->status_code >= 200 && hd->status_code < 300 ) + err = 0; /* Success. */ + else if (hd->status_code == 407) + { + if (opt_debug) + log_debug ("http.c:proxy_connect: 407 seen\n"); + parms = NULL; + for (idx=0; (s = http_get_header (hd, "Proxy-Authenticate", idx)); idx++) + { + if (opt_debug) + log_debug ("http.c:proxy_connect: method=%s\n", s); + if (!parms) + parms = has_leading_keyword (s, "Negotiate"); + } + if (!parms) + authstate = AUTH_NGT_NONE; + else if (authstate == AUTH_NGT_NONE) + authstate = AUTH_NGT_RCVD; + + switch (authstate) + { + case AUTH_NGT_NONE: + if (opt_debug) + log_debug ("http.c:proxy_connect: no supported auth method\n"); + err = gpg_error (GPG_ERR_NO_AUTH); + break; + + case AUTH_NGT_RCVD: + if (opt_debug) + log_debug ("http.c:proxy_connect: using negotiate - init\n"); + err = proxy_get_token (proxy, NULL); + if (err) + goto leave; + if (proxy->outtoklen) /* Authentication needs to continue. */ + { + xfree (authhdr); + authhdr = make_header_line ("Proxy-Authorization: Negotiate ", + "\r\n", + proxy->outtoken, proxy->outtoklen); + if (!authhdr) + { + err = gpg_error_from_syserror (); + goto leave; + } + authstate = AUTH_NGT_SENT; + authpasses++; + goto again; + } + break; + + case AUTH_NGT_SENT: + if (opt_debug) + log_debug ("http.c:proxy_connect: using negotiate - next\n"); + if (!*parms) + { + log_debug ("proxy authentication failed" + " due to server not accepting our challenge\n"); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + } + if (authpasses > 5) + { + log_error ("proxy authentication failed" + " due to too many passes\n"); + err = gpg_error (GPG_ERR_BAD_AUTH); + goto leave; + + } + err = proxy_get_token (proxy, parms); + if (err) + goto leave; + if (proxy->outtoklen) /* Authentication needs to continue. */ + { + xfree (authhdr); + authhdr = make_header_line ("Proxy-Authorization: Negotiate ", + "\r\n", + proxy->outtoken, proxy->outtoklen); + if (!authhdr) + { + err = gpg_error_from_syserror (); + goto leave; + } + authpasses++; + goto again; + } + break; + + default: + BUG(); + } + } + else + err = gpg_error (GPG_ERR_NO_DATA); + + if (err) + { + xfree (tmpstr); + tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port); + log_error (_("error accessing '%s': http status %u\n"), + tmpstr ? tmpstr : "out of core", + http_get_status_code (hd)); + goto leave; + } + + leave: + if (hd->keep_alive) + { + 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; + } + /* Restore flags, destroy stream, reset state. */ + hd->flags = saved_flags; + es_fclose (hd->fp_read); + hd->fp_read = NULL; + hd->read_cookie = NULL; + hd->keep_alive = 0; + hd->in_data = 0; + + xfree (request); + xfree (authhdr); + xfree (tmpstr); + return err; +} + + +/* Make a request string using a standard proxy. On success the + * request is stored at R_REQUEST (and will never be NULL). */ +static gpg_error_t +mk_proxy_request (http_t hd, proxy_info_t proxy, + const char *httphost, const char *server, + unsigned short port, const char *relpath, + const char *authstr, + char **r_request) +{ + gpg_error_t err = 0; + char *authhdr = NULL; + char *request = NULL; + + *r_request = NULL; + + if (proxy->uri->auth + && !(authhdr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + proxy->uri->auth, + strlen (proxy->uri->auth)))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + 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, *relpath == '/' ? "" : "/", relpath, + authstr ? authstr : "", + authhdr ? authhdr : ""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + *r_request = request; + request = NULL; + + leave: + xfree (request); + xfree (authhdr); + return err; +} + + +/* Make a request string using. On success the request is stored at + * R_REQUEST (and will never be NULL). */ +static gpg_error_t +mk_std_request (http_t hd, + const char *httphost, const char *server, + unsigned short port, const char *relpath, + const char *authstr, + char **r_request) +{ + gpg_error_t err = 0; + char portstr[35]; + char *request = NULL; + + *r_request = NULL; + + 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", + *relpath == '/' ? "" : "/", relpath, + httphost? httphost : server, + portstr, + authstr? authstr:""); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + *r_request = request; + request = NULL; + + leave: + xfree (request); + return err; +} + + +/* + * 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 *override_proxy, + const char *srvtag, unsigned int timeout, + strlist_t headers) +{ + gpg_error_t err; + const char *server; + char *request = NULL; + char *relpath = NULL; + unsigned short port; + int use_http_proxy = 0; + char *proxy_authstr = NULL; + char *authstr = NULL; + assuan_fd_t sock; + proxy_info_t proxy = NULL; + + err = send_request_basic_checks (hd); + if (err) + goto leave; + + if ((hd->flags & HTTP_FLAG_FORCE_TOR)) + { + /* 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; + + if ((err = send_request_set_sni (hd, httphost? httphost : server))) + goto leave; + + if ((err = get_proxy_for_url (hd, override_proxy, &proxy))) + goto leave; + + if (proxy && proxy->is_http_proxy) + { + use_http_proxy = 1; /* We want to use a proxy for the connection. */ + err = connect_server (ctrl, + *proxy->uri->host ? proxy->uri->host : "localhost", + proxy->uri->port ? proxy->uri->port : 80, + hd->flags, NULL, timeout, &sock); + } + else + { + err = connect_server (ctrl, + server, port, hd->flags, srvtag, timeout, &sock); + } + if (err) + goto leave; + + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (use_http_proxy && hd->uri->use_tls) + { + err = run_proxy_connect (hd, proxy, httphost, server, port); + if (err) + goto leave; + + /* We are done with the proxy, the code below will establish a + * TLS session and talk directly to the target server. Thus we + * clear the flag to indicate this. */ + use_http_proxy = 0; + } + +#if HTTP_USE_NTBTLS + err = run_ntbtls_handshake (hd); +#elif HTTP_USE_GNUTLS + err = run_gnutls_handshake (hd, server); +#else + err = 0; +#endif + if (err) + goto leave; if (auth || hd->uri->auth) { @@ -2224,9 +2928,8 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, myauth = xtrystrdup (auth); if (!myauth) { - xfree (proxy_authstr); - return gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } remove_escapes (myauth); } @@ -2238,119 +2941,65 @@ send_request (ctrl_t ctrl, http_t hd, const char *httphost, const char *auth, authstr = make_header_line ("Authorization: Basic ", "\r\n", myauth, strlen (myauth)); - if (auth) + if (auth) /* (Was allocated.) */ xfree (myauth); if (!authstr) { - xfree (proxy_authstr); - return gpg_err_make (default_errsource, - gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } } - p = build_rel_path (hd->uri); - if (!p) + relpath = build_rel_path (hd->uri); + if (!relpath) { - xfree (authstr); - xfree (proxy_authstr); - return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + err = gpg_error_from_syserror (); + goto leave; } - 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 : ""); - } + if (use_http_proxy) + err = mk_proxy_request (hd, proxy, httphost, server, port, + relpath, authstr, &request); 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; - } + err = mk_std_request (hd, httphost, server, port, + relpath, authstr, &request); + if (err) + goto leave; 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; + err = make_fp_write (hd, hd->uri->use_tls, hd->session); + if (err) + goto leave; - 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) + if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) { - for (;headers; headers=headers->next) + err = gpg_error_from_syserror (); + goto leave; + } + + 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))) { - 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; - } + err = gpg_error_from_syserror (); + goto leave; } } - } leave: es_free (request); xfree (authstr); xfree (proxy_authstr); + xfree (relpath); + release_proxy_info (proxy); return err; } @@ -2476,19 +3125,26 @@ store_header (http_t hd, char *line) p++; value = p; - for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, line) ) - break; - if (h) + /* Check whether we have already seen a line with that name. In + * that case we assume it is a comma separated list and merge + * them. Of course there are a few exceptions. */ + if (!strcmp (line, "Proxy-Authenticate") + || !strcmp (line, "Www-Authenticate")) + ; /* Better to have them separate. */ + else { - /* 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; + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, line) ) + break; + if (h) + { + 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. */ @@ -2511,18 +3167,27 @@ store_header (http_t hd, char *line) /* 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. */ + * 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. + * SKIP gives the number of entries of the requested NAME to skip + * before returning; this can be used to enumerate headers with the + * same name (see store_header). +*/ const char * -http_get_header (http_t hd, const char *name) +http_get_header (http_t hd, const char *name, unsigned int skip) { header_t h; for (h=hd->headers; h; h = h->next) - if ( !strcmp (h->name, name) ) - return h->value; + if (!strcmp (h->name, name)) + { + if (skip) + skip--; + else + return h->value; + } return NULL; } @@ -2645,7 +3310,7 @@ parse_response (http_t hd) cookie->content_length_valid = 0; if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) { - s = http_get_header (hd, "Content-Length"); + s = http_get_header (hd, "Content-Length", 0); if (s) { cookie->content_length_valid = 1; @@ -2906,7 +3571,7 @@ connect_with_timeout (assuan_fd_t sock, tval.tv_sec = timeout / 1000; tval.tv_usec = (timeout % 1000) * 1000; - n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval); + n = my_select (FD2NUM(sock)+1, &rset, &wset, NULL, &tval); if (n < 0) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); @@ -3101,8 +3766,14 @@ connect_server (ctrl_t ctrl, const char *server, unsigned short port, if (!connected) { if (!hostfound) - log_error ("can't connect to '%s': %s\n", - server, "host not found"); + { + log_error ("can't connect to '%s': %s\n", + server, "host not found"); + /* If the resolver told us "no name" translate this in this + * case to "unknown host". */ + if (gpg_err_code (last_err) == GPG_ERR_NO_NAME) + last_err = 0; + } else if (!anyhostaddr) log_error ("can't connect to '%s': %s\n", server, "no IP address for host"); @@ -3223,31 +3894,48 @@ cookie_read (void *cookie, void *buffer, size_t size) { cookie_t c = cookie; int nread; + size_t offset = 0; if (c->content_length_valid) { if (!c->content_length) - return 0; /* EOF */ + { + c->content_length_valid = 0; + return 0; /* EOF */ + } if (c->content_length < size) size = c->content_length; } + if (c->pending.len) + { + offset = c->pending.len > size? size : c->pending.len; + memcpy (buffer, c->pending.data, offset); + c->pending.len -= offset; + } + + if (offset >= size) + nread = offset; + else #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); + nread = es_fread ((char*)buffer+offset, 1, size-offset, in); if (opt_debug) - log_debug ("TLS network read: %d/%zu\n", nread, size); + log_debug ("TLS network read: %d/%zu\n", nread, size-offset); + if (nread >= 0) + nread += offset; } 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); + nread = gnutls_record_recv (c->session->tls_session, + (char*)buffer+offset, size-offset); if (nread < 0) { if (nread == GNUTLS_E_INTERRUPTED) @@ -3274,11 +3962,86 @@ cookie_read (void *cookie, void *buffer, size_t size) gpg_err_set_errno (EIO); return -1; } + if (nread >= 0) + nread += offset; } else #endif /*HTTP_USE_GNUTLS*/ { - nread = read_server (c->sock->fd, buffer, size); + nread = read_server (c->sock->fd, (char*)buffer+offset, size-offset); + if (opt_debug) + log_debug ("network read: %d/%zu\n", nread, size); + if (nread >= 0) + nread += offset; + } + + if (nread > 0 && c->up_to_empty_line) + { + gpg_error_t err; + const char *s; + size_t n; + int extra; + int lfcr_pending = 0; + char *bufp = buffer; + + if (c->last_was_lf && nread > 1 && bufp[0] == '\r' && bufp[1] == '\n') + { + s = buffer; + extra = 2; + } + else if (c->last_was_lf && bufp[0] == '\r') + { + lfcr_pending = 1; + s = buffer; /* Only to avoid the call to gnupg_memstr. */ + } + else if (c->last_was_lfcr && bufp[0] == '\n') + { + s = buffer; + extra = 1; + } + else + s = NULL; + + c->last_was_lfcr = c->last_was_lf = 0; + + if (!s) + { + s = gnupg_memstr (buffer, nread, "\n\r\n"); + extra = 3; + } + + if (lfcr_pending) + c->last_was_lfcr = 1; + else if (s) + { + /* Save away the rest and return up to the LF. */ + log_assert (!c->pending.len); + n = (s+extra) - bufp; + log_assert (n <= nread); + c->pending.len = nread - n; + if (!c->pending.data || c->pending.len >= c->pending.size) + { + xfree (c->pending.data); + c->pending.size = c->pending.len + 256; /* Some extra space. */ + c->pending.data = xtrymalloc (c->pending.size); + if (!c->pending.data) + { + err = gpg_error_from_syserror (); + log_error ("error allocating network read buffer: %s\n", + gpg_strerror (err)); + return -1; + } + memcpy (c->pending.data, bufp + n, c->pending.len); + } + else + memcpy (c->pending.data, bufp + n, c->pending.len); + nread = n; /* Return everything up to the empty line. */ + c->up_to_empty_line = 0; + } + else if (bufp[nread-1] == '\n') + c->last_was_lf = 1; + else if (nread > 1 && bufp[nread-2] == '\n' && bufp[nread-1] == '\r') + c->last_was_lfcr = 1; } if (c->content_length_valid && nread > 0) @@ -3439,6 +4202,7 @@ cookie_close (void *cookie) if (c->session) http_session_unref (c->session); + xfree (c->pending.data); xfree (c); return 0; } @@ -3447,7 +4211,7 @@ cookie_close (void *cookie) /* Verify the credentials of the server. Returns 0 on success and - store the result in the session object. */ + store the result in the session object. Only used by GNUTLS. */ gpg_error_t http_verify_server_credentials (http_session_t sess) { @@ -3481,19 +4245,15 @@ http_verify_server_credentials (http_session_t sess) } else if (status) { - log_error ("%s: status=0x%04x\n", errprefix, status); -#if GNUTLS_VERSION_NUMBER >= 0x030104 - { - gnutls_datum_t statusdat; + 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*/ + log_error ("%s: status=0x%04x\n", errprefix, status); + if (!gnutls_certificate_verification_status_print + (status, GNUTLS_CRT_X509, &statusdat, 0)) + { + log_info ("%s: %s\n", errprefix, statusdat.data); + gnutls_free (statusdat.data); + } sess->verify.status = status; if (!err) @@ -3643,7 +4403,7 @@ same_host_p (parsed_uri_t a, parsed_uri_t b) } /* Also consider hosts the same if they differ only in a subdomain; - * in both direction. This allows to have redirection between the + * in both direction. This allows one to have redirection between the * WKD advanced and direct lookup methods. */ for (i=0; i < DIM (subdomains); i++) { @@ -3664,8 +4424,8 @@ same_host_p (parsed_uri_t a, parsed_uri_t b) /* 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 + * value of the "Location" header, and R_URL receives the new URL on + * success or NULL on error. Note that INFO->ORIG_URL is * required. */ gpg_error_t http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, @@ -3741,10 +4501,11 @@ http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, http_release_parsed_uri (locuri); return err; } - else if (same_host_p (origuri, locuri)) + else if (!info->restrict_redir || same_host_p (origuri, locuri)) { - /* The host is the same or on an exception list and thus we can - * take the location verbatim. */ + /* Take the syntactically correct location or if restrict_redir + * is set 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); @@ -3754,7 +4515,7 @@ http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, return err; } } - else + else /* Strictly rectricted redirection which we used in the past. */ { /* We take only the host and port from the URL given in the * Location. This limits the effects of redirection attacks by @@ -3833,3 +4594,13 @@ http_status2string (unsigned int status) return ""; } + + +/* Function called on SIGHUP to flush internal variables. */ +void +http_reinitialize (void) +{ +#ifdef HAVE_W32_SYSTEM + w32_get_internet_session (1); /* Clear our session. */ +#endif /*HAVE_W32_SYSTEM*/ +} diff --git a/dirmngr/http.h b/dirmngr/http.h index 18420c925..28406694e 100644 --- a/dirmngr/http.h +++ b/dirmngr/http.h @@ -117,6 +117,7 @@ struct http_redir_info_s unsigned int silent:1; /* No diagnostics. */ unsigned int allow_downgrade:1;/* Allow a downgrade from https to http. */ unsigned int trust_location:1; /* Trust the received Location header. */ + unsigned int restrict_redir:1; /* Use legacy restricted redirection. */ }; typedef struct http_redir_info_s http_redir_info_t; @@ -131,9 +132,11 @@ typedef gpg_error_t (*http_verify_cb_t) (void *opaque, void http_set_verbose (int verbose, int debug); +/* The next three functions are only used with GNUTLS. */ void http_register_tls_callback (gpg_error_t (*cb)(http_t,http_session_t,int)); void http_register_tls_ca (const char *fname); void http_register_cfg_ca (const char *fname); + void http_register_netactivity_cb (void (*cb)(void)); @@ -192,7 +195,7 @@ estream_t http_get_read_ptr (http_t hd); estream_t http_get_write_ptr (http_t hd); unsigned int http_get_status_code (http_t hd); const char *http_get_tls_info (http_t hd, const char *what); -const char *http_get_header (http_t hd, const char *name); +const char *http_get_header (http_t hd, const char *name, unsigned int skip); const char **http_get_header_names (http_t hd); gpg_error_t http_verify_server_credentials (http_session_t sess); diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 002f1a7a5..6be2072e9 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -373,6 +373,8 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers, || !strcmp (uri->parsed_uri->scheme, "ldaps") || !strcmp (uri->parsed_uri->scheme, "ldapi") || uri->parsed_uri->opaque); +#else + (void)newer; #endif if (is_hkp_s || is_http_s || is_ldap) @@ -546,6 +548,40 @@ ks_action_put (ctrl_t ctrl, uri_item_t keyservers, +/* Delete an OpenPGP key from all KEYSERVERS which use LDAP. The key + * is specifified by PATTERNS. */ +gpg_error_t +ks_action_del (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns) +{ + gpg_error_t err = 0; + gpg_error_t first_err = 0; + int any_server = 0; + uri_item_t uri; + + for (uri = keyservers; uri; uri = uri->next) + { +#if USE_LDAP + if ( !strcmp (uri->parsed_uri->scheme, "ldap") + || !strcmp (uri->parsed_uri->scheme, "ldaps") + || !strcmp (uri->parsed_uri->scheme, "ldapi") + || uri->parsed_uri->opaque ) + { + any_server = 1; + err = ks_ldap_del (ctrl, uri->parsed_uri, patterns); + if (err && !first_err) + first_err = err; + } +#endif + } + + if (!any_server) + err = gpg_error (GPG_ERR_NO_KEYSERVER); /* Actual: No LDAP keyserver */ + else if (!err && first_err) + err = first_err; + return err; +} + + /* Query the default LDAP server or the one given by URL using * the filter expression FILTER. Write the result to OUTFP. */ gpg_error_t @@ -590,6 +626,13 @@ ks_action_query (ctrl_t ctrl, const char *url, unsigned int ks_get_flags, return err; #else /* !USE_LDAP */ + (void)ctrl; + (void)url; + (void)ks_get_flags; + (void)filter; + (void)attrs; + (void)newer; + (void)outfp; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index 223aae2da..d222d6afe 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -33,6 +33,8 @@ gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, void *data, size_t datalen, void *info, size_t infolen); +gpg_error_t ks_action_del (ctrl_t ctrl, uri_item_t keyservers, + strlist_t patterns); gpg_error_t ks_action_query (ctrl_t ctrl, const char *ldapserver, unsigned int ks_get_flags, const char *filter, char **attr, diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 5292da844..75fe19987 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -1242,8 +1242,9 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, redirinfo.orig_url = request; redirinfo.orig_onion = uri->onion; redirinfo.allow_downgrade = 1; - /* FIXME: I am not sure whey we allow a downgrade for hkp requests. - * Needs at least an explanation here.. */ + /* FIXME: I am not sure why we allow a downgrade for hkp requests. + * Needs at least an explanation here. */ + redirinfo.restrict_redir = !!(opt.compat_flags & COMPAT_RESTRICT_HTTP_REDIR); once_more: err = http_session_new (&session, httphost, @@ -1326,7 +1327,7 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, { xfree (request_buffer); err = http_prepare_redirect (&redirinfo, http_get_status_code (http), - http_get_header (http, "Location"), + http_get_header (http, "Location", 0), &request_buffer); if (err) goto leave; @@ -1339,18 +1340,17 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr, } goto once_more; - case 501: - err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); - goto leave; - - case 413: /* Payload too large */ - err = gpg_error (GPG_ERR_TOO_LARGE); - goto leave; - default: log_error (_("error accessing '%s': http status %u\n"), request, http_get_status_code (http)); - err = gpg_error (GPG_ERR_NO_DATA); + switch (http_get_status_code (http)) + { + case 401: err = gpg_error (GPG_ERR_NO_AUTH); break; + case 407: err = gpg_error (GPG_ERR_BAD_AUTH); break; + case 413: err = gpg_error (GPG_ERR_TOO_LARGE); break; + case 501: err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; + default: err = gpg_error (GPG_ERR_NO_DATA); break; + } goto leave; } diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c index f55a25774..5091ddf27 100644 --- a/dirmngr/ks-engine-http.c +++ b/dirmngr/ks-engine-http.c @@ -88,6 +88,7 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, redirinfo.orig_onion = uri->onion; redirinfo.orig_https = uri->use_tls; redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE); + redirinfo.restrict_redir = !!(opt.compat_flags & COMPAT_RESTRICT_HTTP_REDIR); /* By default we only use the system provided certificates with this * fetch command. */ @@ -179,7 +180,7 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, { xfree (request_buffer); err = http_prepare_redirect (&redirinfo, http_get_status_code (http), - http_get_header (http, "Location"), + http_get_header (http, "Location", 0), &request_buffer); if (err) goto leave; @@ -192,14 +193,16 @@ ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, } goto once_more; - case 413: /* Payload too large */ - err = gpg_error (GPG_ERR_TOO_LARGE); - goto leave; - default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); - err = gpg_error (GPG_ERR_NO_DATA); + switch (http_get_status_code (http)) + { + case 401: err = gpg_error (GPG_ERR_NO_AUTH); break; + case 407: err = gpg_error (GPG_ERR_BAD_AUTH); break; + case 413: err = gpg_error (GPG_ERR_TOO_LARGE); break; + default: err = gpg_error (GPG_ERR_NO_DATA); break; + } goto leave; } diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c index 1ffd30ecb..ff4f005f4 100644 --- a/dirmngr/ks-engine-ldap.c +++ b/dirmngr/ks-engine-ldap.c @@ -26,6 +26,15 @@ #include #include #include +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# include +# include +# include +# include +#endif #include "dirmngr.h" @@ -44,7 +53,7 @@ #define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/ #define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */ #define SERVERINFO_NTDS 8 /* Server is an Active Directory. */ -#define SERVERINFO_GENERIC 16 /* Connected in genric mode. */ +#define SERVERINFO_GENERIC 16 /* Connected in generic mode. */ /* The page size requested from the server. */ @@ -73,6 +82,9 @@ struct ks_engine_ldap_local_s int more_pages; /* More pages announced by server. */ }; +/*-- prototypes --*/ +static char *map_rid_to_dn (ctrl_t ctrl, const char *rid); +static char *basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri); @@ -142,6 +154,9 @@ epoch2ldaptime (time_t stamp) #endif +/* + * Begin LDAP wrappers + */ static void my_ldap_value_free (char **vals) { @@ -150,6 +165,174 @@ my_ldap_value_free (char **vals) } +/* LDAP wrappers to cope with the stupid use of ULONG instead of int in + * the Windows LDAP interface. rfc1823 alsways uses int and thus + * ldap_parse_result should also do this. */ +#ifdef HAVE_W32_SYSTEM +static int +my_ldap_return_with_check (ULONG l_err) +{ + if ((int)l_err < 0) + { + log_error ("oops: LDAP returned a negative error code (0x%lx)\n", l_err); + l_err = LDAP_OTHER; + } + return (int)l_err; +} +#endif + +static int +my_ldap_parse_result (LDAP *ld, LDAPMessage *result, + int *errcodep, char **matcheddnp, char **errmsgp, + char ***referralsp, LDAPControl ***serverctrlsp, + int freeit) +{ +#ifdef HAVE_W32_SYSTEM + ULONG l_err; + ULONG l_errcode; + l_err = ldap_parse_result (ld, result, + errcodep? &l_errcode : NULL, + matcheddnp, errmsgp, + referralsp, serverctrlsp, freeit); + if (errcodep) + *errcodep = l_errcode; + return my_ldap_return_with_check (l_err); +#else + return ldap_parse_result (ld, result, errcodep, matcheddnp, errmsgp, + referralsp, serverctrlsp, freeit); +#endif +} + + +static int +my_ldap_parse_page_control (LDAP *ld, LDAPControl **ctrls, + int *count, struct berval **cookie) +{ +#ifdef HAVE_W32_SYSTEM + ULONG l_err; + ULONG l_count; + l_err = ldap_parse_page_control (ld, ctrls, count? &l_count: NULL, cookie); + if (count) + *count = l_count; + return my_ldap_return_with_check (l_err); +#else + return ldap_parse_page_control (ld, ctrls, count, cookie); +#endif +} + +/* + * End LDAP wrappers + */ + + +/* Print a description of supported variables. */ +void +ks_ldap_help_variables (ctrl_t ctrl) +{ + const char data[] = + "Supported variables in LDAP filter expressions:\n" + "\n" + "domain - The defaultNamingContext.\n" + "domain_admins - Group of domain admins.\n" + "domain_users - Group with all user accounts.\n" + "domain_guests - Group with the builtin gues account.\n" + "domain_computers - Group with all clients and servers.\n" + "cert_publishers - Group with all cert issuing computers.\n" + "protected_users - Group of users with extra protection.\n" + "key_admins - Group for delegated access to msdsKeyCredentialLink.\n" + "enterprise_key_admins - Similar to key_admins.\n" + "domain_domain_controllers - Group with all domain controllers.\n" + "sid_domain - SubAuthority numbers.\n"; + + ks_print_help (ctrl, data); +} + + +/* Helper function for substitute_vars. */ +static const char * +getval_for_filter (void *cookie, const char *name) +{ + ctrl_t ctrl = cookie; + const char *result = NULL; + + if (!strcmp (name, "sid_domain")) + { +#ifdef HAVE_W32_SYSTEM + PSID mysid; + static char *sidstr; + char *s, *s0; + int i; + + if (!sidstr) + { + mysid = w32_get_user_sid (); + if (!mysid) + { + gpg_err_set_errno (ENOENT); + goto leave; + } + + if (!ConvertSidToStringSid (mysid, &sidstr)) + { + gpg_err_set_errno (EINVAL); + goto leave; + } + /* Example for SIDSTR: + * S-1-5-21-3636969917-2569447256-918939550-1127 */ + for (s0=NULL,s=sidstr,i=0; (s=strchr (s, '-')); i++) + { + s++; + if (i == 3) + s0 = s; + else if (i==6) + { + s[-1] = 0; + break; + } + } + if (!s0) + { + log_error ("oops: invalid SID received from OS"); + gpg_err_set_errno (EINVAL); + LocalFree (sidstr); + goto leave; + } + sidstr = s0; /* (We never release SIDSTR thus no memmove.) */ + } + result = sidstr; +#else + gpg_err_set_errno (ENOSYS); + goto leave; +#endif + } + else if (!strcmp (name, "domain")) + result = basedn_from_rootdse (ctrl, NULL); + else if (!strcmp (name, "domain_admins")) + result = map_rid_to_dn (ctrl, "512"); + else if (!strcmp (name, "domain_users")) + result = map_rid_to_dn (ctrl, "513"); + else if (!strcmp (name, "domain_guests")) + result = map_rid_to_dn (ctrl, "514"); + else if (!strcmp (name, "domain_computers")) + result = map_rid_to_dn (ctrl, "515"); + else if (!strcmp (name, "domain_domain_controllers")) + result = map_rid_to_dn (ctrl, "516"); + else if (!strcmp (name, "cert_publishers")) + result = map_rid_to_dn (ctrl, "517"); + else if (!strcmp (name, "protected_users")) + result = map_rid_to_dn (ctrl, "525"); + else if (!strcmp (name, "key_admins")) + result = map_rid_to_dn (ctrl, "526"); + else if (!strcmp (name, "enterprise_key_admins")) + result = map_rid_to_dn (ctrl, "527"); + else + result = ""; /* Unknown variables are empty. */ + + leave: + return result; +} + + /* Print a help output for the schemata supported by this module. */ gpg_error_t @@ -487,7 +670,7 @@ interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search, * including whether to use TLS and the username and password (see * ldap_parse_uri for a description of the various fields). Be * default a PGP keyserver is assumed; if GENERIC is true a generic - * ldap conenction is instead established. + * ldap connection is instead established. * * Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set * to the base DN for the PGP key space, several flags will be stored @@ -1137,7 +1320,7 @@ return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp) } /* Always print the DN - note that by using only unbkown attributes - * it is pissible to list just the DNs with out addiional + * it is possible to list just the DNs with out additional * linefeeds. */ es_fprintf (*fp, "Dn: %s\n", mydn? mydn : "[oops DN missing]"); @@ -1187,7 +1370,7 @@ return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp) len = values[idx]->bv_len; while (len && (s = memchr (val, '\n', len))) { - s++; /* We als want to print the LF. */ + s++; /* We also want to print the LF. */ if (es_fwrite (val, s - val, 1, *fp) != 1) goto fwrite_failed; len -= (s-val); @@ -1236,10 +1419,11 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, char **attrs, LDAPMessage **r_message) { gpg_error_t err = 0; - int l_err, l_reserr; + int l_err; + int l_reserr; + unsigned int totalcount = 0; LDAPControl *srvctrls[2] = { NULL, NULL }; int count; - unsigned int totalcount = 0; LDAPControl *pagectrl = NULL; LDAPControl **resctrls = NULL; @@ -1279,8 +1463,8 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, if (ctrl->ks_get_state) { - l_err = ldap_parse_result (ldap_conn, *r_message, &l_reserr, - NULL, NULL, NULL, &resctrls, 0); + l_err = my_ldap_parse_result (ldap_conn, *r_message, &l_reserr, + NULL, NULL, NULL, &resctrls, 0); if (l_err) { err = ldap_err_to_gpg_err (l_err); @@ -1294,9 +1478,9 @@ search_and_parse (ctrl_t ctrl, const char *keyspec, ber_bvfree (ctrl->ks_get_state->pagecookie); ctrl->ks_get_state->pagecookie = NULL; } - l_err = ldap_parse_page_control (ldap_conn, resctrls, - &totalcount, - &ctrl->ks_get_state->pagecookie); + l_err = my_ldap_parse_page_control (ldap_conn, resctrls, + &totalcount, + &ctrl->ks_get_state->pagecookie); if (l_err) { err = ldap_err_to_gpg_err (l_err); @@ -1396,6 +1580,63 @@ fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri) } +/* Return the DN for the given RID. This is used with the Active + * Directory. */ +static char * +map_rid_to_dn (ctrl_t ctrl, const char *rid) +{ + gpg_error_t err; + char *result = NULL; + estream_t infp = NULL; + uri_item_t puri; /* The broken down URI. */ + nvc_t nvc = NULL; + char *filter = NULL; + const char *s; + char *attr[2] = {"dn", NULL}; + + err = ks_action_parse_uri ("ldap:///", &puri); + if (err) + return NULL; + + filter = strconcat ("(objectSid=S-1-5-21-$sid_domain-", rid, ")", NULL); + if (!filter) + goto leave; + + err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_SUBST, + filter, attr, NULL, &infp); + if (err) + { + log_error ("ldap: AD query '%s' failed: %s\n", filter,gpg_strerror (err)); + goto leave; + } + if ((err = nvc_parse (&nvc, NULL, infp))) + { + log_error ("ldap: parsing the result failed: %s\n",gpg_strerror (err)); + goto leave; + } + if (!(s = nvc_get_string (nvc, "Dn:"))) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + log_error ("ldap: mapping rid '%s'failed: %s\n", rid, gpg_strerror (err)); + goto leave; + } + result = xtrystrdup (s); + if (!result) + { + err = gpg_error_from_syserror (); + log_error ("ldap: strdup failed: %s\n", gpg_strerror (err)); + goto leave; + } + + leave: + es_fclose (infp); + release_uri_item_list (puri); + xfree (filter); + nvc_release (nvc); + return result; +} + + /* Return the baseDN for URI which might have already been cached for * this session. */ static char * @@ -2184,7 +2425,7 @@ modlist_free (LDAPMod **modlist) LDAPMod *mod = *ml; char **ptr; - /* The list of values is a NULL termianted array of pointers. + /* The list of values is a NULL terminated array of pointers. If the list is NULL, there are no values. */ if (mod->mod_values) @@ -2283,7 +2524,7 @@ uncescape (char *str) /* Given one line from an info block (`gpg --list-{keys,sigs} --with-colons KEYID'), pull it apart and fill in the modlist with the relevant (for the LDAP schema) attributes. EXTRACT_STATE - should initally be set to 0 by the caller. SCHEMAV2 is set if the + should initially be set to 0 by the caller. SCHEMAV2 is set if the server supports the version 2 schema. */ static void extract_attributes (LDAPMod ***modlist, int *extract_state, @@ -2443,7 +2684,7 @@ extract_attributes (LDAPMod ***modlist, int *extract_state, memset (&tm, 0, sizeof (tm)); - /* parse_timestamp handles both seconds fromt he epoch and + /* parse_timestamp handles both seconds from the epoch and ISO 8601 format. We also need to handle YYYY-MM-DD format (as generated by gpg1 --with-colons --list-key). Check that first and then if it fails, then try @@ -2491,7 +2732,7 @@ extract_attributes (LDAPMod ***modlist, int *extract_state, memset (&tm, 0, sizeof (tm)); - /* parse_timestamp handles both seconds fromt he epoch and + /* parse_timestamp handles both seconds from the epoch and ISO 8601 format. We also need to handle YYYY-MM-DD format (as generated by gpg1 --with-colons --list-key). Check that first and then if it fails, then try @@ -2807,6 +3048,18 @@ ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, } +/* Delete the keys given by PATTERNS from the keyserver identified by + * URI. */ +gpg_error_t +ks_ldap_del (ctrl_t ctrl, parsed_uri_t uri, strlist_t patterns) +{ + (void)ctrl; + (void)uri; + (void)patterns; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +} + + /* Get the data described by FILTER_ARG from URI. On success R_FP has * an open stream to read the data. KS_GET_FLAGS conveys flags from @@ -2824,6 +3077,7 @@ ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, LDAP *ldap_conn = NULL; char *basedn = NULL; estream_t fp = NULL; + char *filter_arg_buffer = NULL; char *filter = NULL; int scope = LDAP_SCOPE_SUBTREE; LDAPMessage *message = NULL; @@ -2839,6 +3093,20 @@ ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, if ((!filter_arg || !*filter_arg) && (ks_get_flags & KS_GET_FLAG_ROOTDSE)) filter_arg = "^&base&(objectclass=*)"; + if ((ks_get_flags & KS_GET_FLAG_SUBST) + && filter_arg && strchr (filter_arg, '$')) + { + filter_arg_buffer = substitute_vars (filter_arg, getval_for_filter, ctrl); + if (!filter_arg_buffer) + { + err = gpg_error_from_syserror (); + log_error ("substituting filter variables failed: %s\n", + gpg_strerror (err)); + goto leave; + } + filter_arg = filter_arg_buffer; + } + err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode); if (err) goto leave; @@ -3048,6 +3316,7 @@ ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, ldap_unbind (ldap_conn); xfree (filter); + xfree (filter_arg_buffer); return err; } diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index 03588a4d3..dfc626d56 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -29,6 +29,7 @@ #define KS_GET_FLAG_NEXT 4 #define KS_GET_FLAG_ONLY_AD 8 /* Do this only if we have an AD. */ #define KS_GET_FLAG_ROOTDSE 16 /* Get the rootDSE. */ +#define KS_GET_FLAG_SUBST 32 /* Substiture variables. */ /*-- ks-action.c --*/ @@ -70,6 +71,7 @@ gpg_error_t ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri); gpg_error_t ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp); /*-- ks-engine-ldap.c --*/ +void ks_ldap_help_variables (ctrl_t ctrl); gpg_error_t ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri); void ks_ldap_free_state (struct ks_engine_ldap_local_s *state); gpg_error_t ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, @@ -80,6 +82,7 @@ gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, void *data, size_t datalen, void *info, size_t infolen); +gpg_error_t ks_ldap_del (ctrl_t ctrl, parsed_uri_t uri, strlist_t patterns); gpg_error_t ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags, const char *filter, char **attrs, diff --git a/dirmngr/ldap-misc.c b/dirmngr/ldap-misc.c index 6b0939a3b..a3aef0e62 100644 --- a/dirmngr/ldap-misc.c +++ b/dirmngr/ldap-misc.c @@ -220,7 +220,7 @@ ldap_to_gpg_err (LDAP *ld) * ^&SCOPE&(objectClasses=*) * * Give a scope and a filter. Note that R_SCOPE is only changed if a - * STRING has scope parameter. Setting this initally to -1 allows to + * STRING has scope parameter. Setting this initially to -1 allows to * detect this case. */ gpg_error_t @@ -380,13 +380,14 @@ rfc4517toisotime (gnupg_isotime_t timebuf, const char *string) int year, month, day, hour, minu, sec; const char *s; + /* Sample value: "20230823141623Z"; */ for (i=0, s=string; i < 10; i++, s++) /* Need yyyymmddhh */ if (!digitp (s)) return gpg_error (GPG_ERR_INV_TIME); year = atoi_4 (string); month = atoi_2 (string + 4); day = atoi_2 (string + 6); - hour = atoi_2 (string + 9); + hour = atoi_2 (string + 8); minu = 0; sec = 0; if (digitp (s) && digitp (s+1)) diff --git a/dirmngr/ldap-parse-uri.c b/dirmngr/ldap-parse-uri.c index 573bcc77f..5856c1b4e 100644 --- a/dirmngr/ldap-parse-uri.c +++ b/dirmngr/ldap-parse-uri.c @@ -162,7 +162,7 @@ ldap_parse_uri (parsed_uri_t *purip, const char *uri) if (password) { - puri->query = calloc (sizeof (*puri->query), 1); + puri->query = calloc (1, sizeof (*puri->query)); if (!puri->query) { err = gpg_err_code_from_syserror (); diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c index 23d514cf9..c9b7eada1 100644 --- a/dirmngr/ldap-wrapper.c +++ b/dirmngr/ldap-wrapper.c @@ -60,7 +60,6 @@ #include #include "dirmngr.h" -#include "../common/exechelp.h" #include "misc.h" #include "ldap-wrapper.h" @@ -87,7 +86,7 @@ struct wrapper_context_s { struct wrapper_context_s *next; - pid_t pid; /* The pid of the wrapper process. */ + gpgrt_process_t proc;/* 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. */ @@ -170,10 +169,10 @@ read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) static void destroy_wrapper (struct wrapper_context_s *ctx) { - if (ctx->pid != (pid_t)(-1)) + if (ctx->proc) { - gnupg_kill_process (ctx->pid); - gnupg_release_process (ctx->pid); + gpgrt_process_terminate (ctx->proc); + gpgrt_process_release (ctx->proc); } ksba_reader_release (ctx->reader); SAFE_CLOSE (ctx->fp); @@ -260,7 +259,7 @@ read_log_data (struct wrapper_context_s *ctx) 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)); + ctx->printable_pid, gpg_strerror (err)); } print_log_line (ctx, NULL); /* Flush. */ SAFE_CLOSE (ctx->log_fp); @@ -438,50 +437,44 @@ ldap_reaper_thread (void *dummy) } /* Check whether the process is still running. */ - if (ctx->pid != (pid_t)(-1)) + if (ctx->proc) { - int status; - - err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, - &status); + err = gpgrt_process_wait (ctx->proc, 0); if (!err) { + int status; + + gpgrt_process_ctl (ctx->proc, GPGRT_PROCESS_GET_EXIT_ID, + &status); if (DBG_EXTPROG) - log_info (_("ldap wrapper %d ready"), (int)ctx->pid); + log_info (_("ldap wrapper %d ready"), ctx->printable_pid); ctx->ready = 1; - gnupg_release_process (ctx->pid); - ctx->pid = (pid_t)(-1); + gpgrt_process_release (ctx->proc); + ctx->proc = NULL; 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); + ctx->printable_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; + ctx->printable_pid, status); } 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)); + ctx->printable_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) + if (ctx->proc && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) { - gnupg_kill_process (ctx->pid); + gpgrt_process_terminate (ctx->proc); ctx->stamp = (time_t)(-1); log_info (_("ldap wrapper %d stalled - killing\n"), - (int)ctx->pid); + ctx->printable_pid); /* We need to close the log stream because the cleanup * loop waits for it. */ SAFE_CLOSE (ctx->log_fp); @@ -496,10 +489,10 @@ ldap_reaper_thread (void *dummy) { 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" + log_debug (" c=%p pid=%d rdr=%p logfp=%p" " ctrl=%p/%d la=%lu rdy=%d\n", ctx, - (int)ctx->pid, (int)ctx->printable_pid, + ctx->printable_pid, ctx->reader, ctx->log_fp, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, (unsigned long)ctx->stamp, ctx->ready); @@ -602,9 +595,9 @@ ldap_wrapper_release_context (ksba_reader_t reader) if (ctx->reader == reader) { if (DBG_EXTPROG) - log_debug ("releasing ldap worker c=%p pid=%d/%d rdr=%p" + log_debug ("releasing ldap worker c=%p pid=%d rdr=%p" " ctrl=%p/%d\n", ctx, - (int)ctx->pid, (int)ctx->printable_pid, + ctx->printable_pid, ctx->reader, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); @@ -639,8 +632,8 @@ ldap_wrapper_connection_cleanup (ctrl_t ctrl) { ctx->ctrl->refcount--; ctx->ctrl = NULL; - if (ctx->pid != (pid_t)(-1)) - gnupg_kill_process (ctx->pid); + if (ctx->proc) + gpgrt_process_terminate (ctx->proc); if (ctx->fp_err) log_info ("%s: reading from ldap wrapper %d failed: %s\n", __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); @@ -798,7 +791,7 @@ gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) { gpg_error_t err; - pid_t pid; + gpgrt_process_t process; struct wrapper_context_s *ctx; int i; int j; @@ -854,19 +847,22 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) return err; } - err = gnupg_spawn_process (pgmname, arg_list, - NULL, GNUPG_SPAWN_NONBLOCK, - NULL, &outfp, &errfp, &pid); + err = gpgrt_process_spawn (pgmname, arg_list, + (GPGRT_PROCESS_STDOUT_PIPE + | GPGRT_PROCESS_STDERR_PIPE), + NULL, &process); if (err) { - xfree (arg_list); + xfree (arg_list); xfree (ctx); log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); return err; } + gpgrt_process_get_streams (process, GPGRT_PROCESS_STREAM_NONBLOCK, + NULL, &outfp, &errfp); + gpgrt_process_ctl (process, GPGRT_PROCESS_GET_PROC_ID, &ctx->printable_pid); - ctx->pid = pid; - ctx->printable_pid = (int) pid; + ctx->proc = process; ctx->fp = outfp; ctx->log_fp = errfp; ctx->ctrl = ctrl; @@ -902,7 +898,7 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) if (DBG_EXTPROG) { log_debug ("ldap wrapper %d started (%p, %s)", - (int)ctx->pid, ctx->reader, pgmname); + ctx->printable_pid, ctx->reader, pgmname); for (i=0; arg_list[i]; i++) log_printf (" [%s]", arg_list[i]); log_printf ("\n"); diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index b80012d03..c4bb60ba5 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -31,7 +31,6 @@ #include #include "dirmngr.h" -#include "../common/exechelp.h" #include "crlfetch.h" #include "ldapserver.h" #include "misc.h" @@ -256,7 +255,7 @@ url_fetch_ldap (ctrl_t ctrl, const char *url, ksba_reader_t *reader) } if (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps")) - tls_mode = 2; /* LDAP-over-TLS here becuase we get it from certs. */ + tls_mode = 2; /* LDAP-over-TLS here because we get it from certs. */ else tls_mode = 0; @@ -524,7 +523,7 @@ make_one_filter (const char *pattern, char **r_result) if (*pattern) { /* We need just the BaseDN. This assumes that the Subject - * is correcly stored in the DT. This is however not always + * is correctly stored in the DT. This is however not always * the case and the actual DN is different from the * subject. In this case we won't find anything. */ if (extfilt_need_escape (pattern) @@ -606,7 +605,7 @@ make_one_filter (const char *pattern, char **r_result) /* Prepare an LDAP query to return the cACertificate attribute for DN. * All configured default servers are queried until one responds. * This function returns an error code or 0 and stored a newly - * allocated contect object at CONTEXT on success. */ + * allocated context object at CONTEXT on success. */ gpg_error_t start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, const char *dn) @@ -778,7 +777,7 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, if (argc >= DIM (argv) - 1) { /* Too many patterns. It does not make sense to allow an - arbitrary number of patters because the length of the + arbitrary number of patterns because the length of the command line is limited anyway. */ err = gpg_error (GPG_ERR_RESOURCE_LIMIT); goto leave; diff --git a/dirmngr/ldapserver.c b/dirmngr/ldapserver.c index ed38c7101..8cd193f86 100644 --- a/dirmngr/ldapserver.c +++ b/dirmngr/ldapserver.c @@ -60,7 +60,7 @@ ldapserver_list_free (ldap_server_t servers) * Flags are: * * starttls := Use STARTTLS with a default port of 389 - * ldaptls := Tunnel LDAP trough a TLS tunnel with default port 636 + * ldaptls := Tunnel LDAP through a TLS tunnel with default port 636 * plain := Switch to plain unsecured LDAP. * (The last of these 3 flags is the effective one) * ntds := Use Active Directory authentication diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 9cedf911c..d1830237d 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -583,7 +583,7 @@ gpg_error_t armor_data (char **r_string, const void *data, size_t datalen) { gpg_error_t err; - struct b64state b64state; + gpgrt_b64state_t b64state; estream_t fp; long length; char *buffer; @@ -595,9 +595,15 @@ armor_data (char **r_string, const void *data, size_t datalen) if (!fp) return gpg_error_from_syserror (); - if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK")) - || (err=b64enc_write (&b64state, data, datalen)) - || (err = b64enc_finish (&b64state))) + b64state = gpgrt_b64enc_start (fp, "PGP PUBLIC KEY BLOCK"); + if (!b64state) + { + es_fclose (fp); + return gpg_error_from_syserror (); + } + + if ((err = gpgrt_b64enc_write (b64state, data, datalen)) + || (err = gpgrt_b64enc_finish (b64state))) { es_fclose (fp); return err; diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index 483b6f32d..7de8b10e4 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -31,7 +31,7 @@ #include "certcache.h" #include "ocsp.h" -/* The maximum size we allow as a response from an OCSP reponder. */ +/* The maximum size we allow as a response from an OCSP responder. */ #define MAX_RESPONSE_SIZE 65536 @@ -227,7 +227,7 @@ do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, case 301: case 302: { - const char *s = http_get_header (http, "Location"); + const char *s = http_get_header (http, "Location", 0); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); @@ -526,7 +526,7 @@ check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, /* 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 + key. If SIGNER_FPR_LIST is not NULL, the default OCSP responder has been used and thus the certificate is one of those identified by the fingerprints. */ static gpg_error_t @@ -651,7 +651,7 @@ check_signature (ctrl_t ctrl, 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. If R_REVOKED_AT or R_REASON are not - NULL and the certificat has been revoked the revocation time and + NULL and the certificate has been revoked the revocation time and the reasons are stored there. */ gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, @@ -723,7 +723,7 @@ ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, } /* Figure out the OCSP responder to use. - 1. Try to get the reponder from the certificate. + 1. Try to get the responder from the certificate. We do only take http and https style URIs into account. 2. If this fails use the default responder, if any. */ diff --git a/dirmngr/server.c b/dirmngr/server.c index 2c5a41b07..3ad939a6b 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -32,6 +32,13 @@ #include #include #include +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# include +# include +#endif #include "dirmngr.h" #include @@ -317,6 +324,46 @@ strcpy_escaped_plus (char *d, const unsigned char *s) } +/* Break the LINE into space delimited tokens, put them into a new + * strlist and return it at R_LIST. On error an erro code is + * returned. If no tokens are found the list is set to NULL. + * Percent-plus encoding is removed from each token. Note that the + * function will modify LINE. */ +static gpg_error_t +percentplus_line_to_strlist (char *line, strlist_t *r_list) +{ + strlist_t list = NULL; + strlist_t sl; + char *p; + + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + gpg_error_t err = gpg_error_from_syserror (); + free_strlist (list); + *r_list = NULL; + return err; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + *r_list = list; + return 0; +} + + /* This function returns true if a Tor server is running. The status * is cached for the current connection. */ static int @@ -925,7 +972,7 @@ proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) err = get_dns_srv (ctrl, domain, "openpgpkey", NULL, &srvs, &srvscount); if (err) { - /* Ignore server failed becuase there are too many resolvers + /* Ignore server failed because there are too many resolvers * which do not work as expected. */ if (gpg_err_code (err) == GPG_ERR_SERVER_FAILED) err = 0; /*(srvcount is guaranteed to be 0)*/ @@ -2195,18 +2242,30 @@ ensure_keyserver (ctrl_t ctrl) uri_item_t plain_items = NULL; uri_item_t ui; strlist_t sl; + int none_seen = 1; if (ctrl->server_local->keyservers) return 0; /* Already set for this session. */ if (!opt.keyserver) { /* No global option set. Fall back to default: */ - return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER, - &ctrl->server_local->keyservers); + /* return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER, */ + /* &ctrl->server_local->keyservers); */ + err = gpg_error (GPG_ERR_NO_KEYSERVER); /* No more default. */ + goto leave; } for (sl = opt.keyserver; sl; sl = sl->next) { + /* Frontends like Kleopatra may prefix option values without a + * scheme with "hkps://". Thus we need to check that too. + * Nobody will be mad enough to call a machine "none". */ + if (!strcmp (sl->d, "none") || !strcmp (sl->d, "hkp://none") + || !strcmp (sl->d, "hkps://none")) + { + none_seen = 1; + continue; + } err = make_keyserver_item (sl->d, &item); if (err) goto leave; @@ -2222,6 +2281,12 @@ ensure_keyserver (ctrl_t ctrl) } } + if (none_seen && !plain_items && !onion_items) + { + err = gpg_error (GPG_ERR_NO_KEYSERVER); + goto leave; + } + /* Decide which to use. Note that the session has no keyservers yet set. */ if (onion_items && !onion_items->next && plain_items && !plain_items->next) @@ -2292,8 +2357,7 @@ cmd_keyserver (assuan_context_t ctx, char *line) gpg_error_t err = 0; int clear_flag, add_flag, help_flag, host_flag, resolve_flag; int dead_flag, alive_flag; - uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it - is always initialized. */ + uri_item_t item = NULL; clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); @@ -2359,13 +2423,17 @@ cmd_keyserver (assuan_context_t ctx, char *line) if (add_flag) { - err = make_keyserver_item (line, &item); + if (!strcmp (line, "none") || !strcmp (line, "hkp://none") + || !strcmp (line, "hkps://none")) + err = 0; + else + err = make_keyserver_item (line, &item); if (err) goto leave; } if (clear_flag) release_ctrl_keyservers (ctrl); - if (add_flag) + if (add_flag && item) { item->next = ctrl->server_local->keyservers; ctrl->server_local->keyservers = item; @@ -2406,37 +2474,16 @@ cmd_ks_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; - strlist_t list, sl; - char *p; + strlist_t list; estream_t outfp; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; line = skip_options (line); - /* Break the line down into an strlist. Each pattern is - percent-plus escaped. */ - list = NULL; - for (p=line; *p; line = p) - { - while (*p && *p != ' ') - p++; - if (*p) - *p++ = 0; - if (*line) - { - sl = xtrymalloc (sizeof *sl + strlen (line)); - if (!sl) - { - err = gpg_error_from_syserror (); - goto leave; - } - sl->flags = 0; - strcpy_escaped_plus (sl->d, line); - sl->next = list; - list = sl; - } - } + err = percentplus_line_to_strlist (line, &list); + if (err) + goto leave; err = ensure_keyserver (ctrl); if (err) @@ -2475,9 +2522,7 @@ cmd_ks_get (assuan_context_t ctx, char *line) ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list = NULL; - strlist_t sl; const char *s; - char *p; estream_t outfp; unsigned int flags = 0; gnupg_isotime_t opt_newer; @@ -2500,30 +2545,13 @@ cmd_ks_get (assuan_context_t ctx, char *line) } line = skip_options (line); - /* Break the line into a strlist. Each pattern is by - definition percent-plus escaped. However we only support keyids - and fingerprints and thus the client has no need to apply the - escaping. */ - for (p=line; *p; line = p) - { - while (*p && *p != ' ') - p++; - if (*p) - *p++ = 0; - if (*line) - { - sl = xtrymalloc (sizeof *sl + strlen (line)); - if (!sl) - { - err = gpg_error_from_syserror (); - goto leave; - } - sl->flags = 0; - strcpy_escaped_plus (sl->d, line); - sl->next = list; - list = sl; - } - } + /* Break the line into a strlist. Each pattern is by definition + percent-plus escaped. However we only support keyids and + fingerprints and thus the caler of this function has no need to + apply the escaping. */ + err = percentplus_line_to_strlist (line, &list); + if (err) + goto leave; if ((flags & KS_GET_FLAG_FIRST) && !(flags & KS_GET_FLAG_ONLY_LDAP)) { @@ -2606,10 +2634,6 @@ cmd_ks_fetch (assuan_context_t ctx, char *line) ctrl->timeout = opt.connect_quick_timeout; line = skip_options (line); - err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */ - if (err) - goto leave; - /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) @@ -2624,7 +2648,6 @@ cmd_ks_fetch (assuan_context_t ctx, char *line) ctrl->server_local->inhibit_data_logging = 0; } - leave: return leave_cmd (ctx, err); } @@ -2699,17 +2722,69 @@ cmd_ks_put (assuan_context_t ctx, char *line) } +static const char hlp_ks_del[] = + "KS_DEL --ldap {}\n" + "\n" + "Delete the keys matching PATTERN from the configured OpenPGP LDAP server\n" + "The pattern should be a fingerprint.\n" + "The option --ldap is mandatory.\n"; +static gpg_error_t +cmd_ks_del (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + strlist_t list = NULL; + unsigned int flags = 0; + + if (has_option (line, "--ldap")) + flags |= KS_GET_FLAG_ONLY_LDAP; + line = skip_options (line); + + err = percentplus_line_to_strlist (line, &list); + if (err) + goto leave; + + if (!(flags & KS_GET_FLAG_ONLY_LDAP)) + { + err = set_error (GPG_ERR_SYNTAX, "option --ldap is mandatory"); + goto leave; + } + + if (!list) + { + err = set_error (GPG_ERR_SYNTAX, "no fingerprints given"); + goto leave; + } + + err = ensure_keyserver (ctrl); + if (err) + goto leave; + + err = ks_action_del (ctrl, ctrl->server_local->keyservers, list); + + leave: + free_strlist (list); + return leave_cmd (ctx, err); +} + + static const char hlp_ad_query[] = - "AD_QUERY [--first|--next] [--] \n" + "AD_QUERY [--first|--next] [--] \n" "\n" "Query properties from a Windows Active Directory.\n" - "Our extended filter syntax may be used for the filter\n" - "expression; see gnupg/dirmngr/ldap-misc.c. There are\n" - "a couple of other options available:\n\n" - " --rootdse - Query the root using serverless binding,\n" + "Options:\n" + "\n" + " --rootdse - Query the root using serverless binding,\n" + " --subst - Substitute variables in the filter\n" " --attr= - Comma delimited list of attributes\n" " to return.\n" + " --help - List supported variables\n" + "\n" + "Extended filter syntax is allowed:\n" + " ^[][&]&[]\n" + "Usual escaping rules apply. An ampersand in must\n" + "doubled. may be \"base\", \"one\", or \"sub\"." ; static gpg_error_t cmd_ad_query (assuan_context_t ctx, char *line) @@ -2723,6 +2798,7 @@ cmd_ad_query (assuan_context_t ctx, char *line) char **opt_attr = NULL; const char *s; gnupg_isotime_t opt_newer; + int opt_help = 0; *opt_newer = 0; @@ -2733,6 +2809,10 @@ cmd_ad_query (assuan_context_t ctx, char *line) flags |= KS_GET_FLAG_NEXT; if (has_option (line, "--rootdse")) flags |= KS_GET_FLAG_ROOTDSE; + if (has_option (line, "--subst")) + flags |= KS_GET_FLAG_SUBST; + if (has_option (line, "--help")) + opt_help = 1; if ((s = option_value (line, "--newer")) && !string2isotime (opt_newer, s)) { @@ -2756,6 +2836,15 @@ cmd_ad_query (assuan_context_t ctx, char *line) line = skip_options (line); filter = line; + if (opt_help) + { +#if USE_LDAP + ks_ldap_help_variables (ctrl); +#endif + err = 0; + goto leave; + } + if ((flags & KS_GET_FLAG_NEXT)) { if (*filter || (flags & ~KS_GET_FLAG_NEXT)) @@ -2907,14 +2996,39 @@ cmd_getinfo (assuan_context_t ctx, char *line) { 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)); + { + err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); + goto leave; + } + err = assuan_send_data (ctx, s, strlen (s)); } } +#ifdef HAVE_W32_SYSTEM + else if (!strcmp (line, "sid")) + { + PSID mysid; + char *sidstr; + + mysid = w32_get_user_sid (); + if (!mysid) + { + err = set_error (GPG_ERR_NOT_FOUND, "Error getting my SID"); + goto leave; + } + + if (!ConvertSidToStringSid (mysid, &sidstr)) + { + err = set_error (GPG_ERR_BUG, "Error converting SID to a string"); + goto leave; + } + err = assuan_send_data (ctx, sidstr, strlen (sidstr)); + LocalFree (sidstr); + } +#endif /*HAVE_W32_SYSTEM*/ else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + leave: return leave_cmd (ctx, err); } @@ -2994,6 +3108,7 @@ register_commands (assuan_context_t ctx) { "KS_GET", cmd_ks_get, hlp_ks_get }, { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, + { "KS_DEL", cmd_ks_del, hlp_ks_del }, { "AD_QUERY", cmd_ad_query, hlp_ad_query }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, @@ -3254,7 +3369,7 @@ dirmngr_status_help (ctrl_t ctrl, const char *text) /* Print a help status line using a printf like format. The function - * splits text at LFs. With CTRL beeing NULL, the function behaves + * splits text at LFs. With CTRL being NULL, the function behaves * like log_info. */ gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) diff --git a/dirmngr/t-http-basic.c b/dirmngr/t-http-basic.c index edf82efb9..ba3d07a8c 100644 --- a/dirmngr/t-http-basic.c +++ b/dirmngr/t-http-basic.c @@ -165,6 +165,7 @@ test_http_prepare_redirect (void) ri.silent = 1; ri.redirects_left = 1; ri.orig_url = tests[tidx].url; + ri.restrict_redir = 1; /* This is what we used to test here. */ err = http_prepare_redirect (&ri, 301, tests[tidx].location, &newurl); if (err && newurl) diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c index 7f3aa005d..3cc4be23a 100644 --- a/dirmngr/t-http.c +++ b/dirmngr/t-http.c @@ -288,6 +288,11 @@ main (int argc, char **argv) my_http_flags |= HTTP_FLAG_FORCE_TOR; argc--; argv++; } + else if (!strcmp (*argv, "--try-proxy")) + { + my_http_flags |= HTTP_FLAG_TRY_PROXY; + argc--; argv++; + } else if (!strcmp (*argv, "--no-out")) { no_out = 1; @@ -458,7 +463,7 @@ main (int argc, char **argv) log_fatal ("http_get_header_names failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); for (i = 0; names[i]; i++) - printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i])); + printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i], 0)); xfree (names); } fflush (stdout); @@ -484,7 +489,7 @@ main (int argc, char **argv) case 301: case 302: case 307: - log_info ("Redirected to: %s\n", http_get_header (hd, "Location")); + log_info ("Redirected to: %s\n", http_get_header (hd, "Location", 0)); break; } http_close (hd, 0); diff --git a/dirmngr/t-ldap-parse-uri.c b/dirmngr/t-ldap-parse-uri.c index 984e1412f..e46bbee83 100644 --- a/dirmngr/t-ldap-parse-uri.c +++ b/dirmngr/t-ldap-parse-uri.c @@ -291,7 +291,7 @@ main (int argc, char **argv) } if (argc) { - fprintf (stderr, PGM ": no argumenst are expected\n"); + fprintf (stderr, PGM ": no arguments are expected\n"); exit (1); } diff --git a/dirmngr/validate.c b/dirmngr/validate.c index 02db3c270..94a468b38 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -42,7 +42,7 @@ enum cert_usage_modes CERT_USAGE_MODE_VRFY, /* Usable for verification. */ CERT_USAGE_MODE_DECR, /* Usable for decryption. */ CERT_USAGE_MODE_CERT, /* Usable for cert signing. */ - CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */ + CERT_USAGE_MODE_OCSP, /* Usable for OCSP response signing. */ CERT_USAGE_MODE_CRL /* Usable for CRL signing. */ }; @@ -56,7 +56,7 @@ struct chain_item_s ksba_cert_t cert; /* The certificate. */ unsigned char fpr[20]; /* Fingerprint of the certificate. */ int is_self_signed; /* This certificate is self-signed. */ - int is_valid; /* The certifiate is valid except for revocations. */ + int is_valid; /* The certificate is valid except for revocations. */ }; typedef struct chain_item_s *chain_item_t; @@ -173,7 +173,7 @@ check_cert_policy (ksba_cert_t cert) if (err) return err; - /* STRING is a line delimited list of certifiate policies as stored + /* 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 */ diff --git a/doc/DETAILS b/doc/DETAILS index fd95e511c..0504c80bb 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -132,7 +132,7 @@ described here. *** Field 5 - KeyID This is the 64 bit keyid as specified by OpenPGP and the last 64 - bit of the SHA-1 fingerprint of an X.509 certifciate. + bit of the SHA-1 fingerprint of an X.509 certificate. *** Field 6 - Creation date @@ -160,18 +160,24 @@ described here. This is only used on primary keys. This is a single letter, but be prepared that additional information may follow in future - versions. For trust signatures with a regular expression, this is - the regular expression value, quoted as in field 10. + versions. Note that if a trust signature indicates that the key's + computed trust is higher than the ownertrust, that higher value is + shown here. + + In signature records describing a trust signatures this is the + regular expression value, quoted as in field 10. *** Field 10 - User-ID The value is quoted like a C string to avoid control characters (the colon is quoted =\x3a=). For a "pub" record this field is - not used on --fixed-list-mode. A UAT record puts the attribute + not used on --fixed-list-mode. A "uat" record puts the attribute subpacket count here, a space, and then the total attribute - subpacket size. In gpgsm the issuer name comes here. The FPR and FP2 - records store the fingerprints here. The fingerprint of a - revocation key is stored here. + subpacket size. In gpgsm the issuer name comes here. The FPR and + FP2 records store the fingerprints here. The fingerprint of a + revocation key is also stored here. A "grp" records puts the + keygrip here; for combined algorithms the keygrips are delimited + by comma. *** Field 11 - Signature class @@ -243,7 +249,9 @@ described here. *** Field 17 - Curve name For pub, sub, sec, ssb, crt, and crs records this field is used - for the ECC curve name. + for the ECC curve name. For composite algorithms the first and + the second algorithm name, delimited by an underscore, are put + here. *** Field 18 - Compliance flags @@ -254,6 +262,8 @@ described here. - 8 :: The key is compliant with RFC4880bis - 23 :: The key is compliant with compliance mode "de-vs". + - 2023 :: The key is compliant with a compliance mode "de-vs" but + the software has not yet been approved. - 6001 :: Screening hit on the ROCA vulnerability. *** Field 19 - Last update @@ -268,13 +278,30 @@ described here. The origin of the key or the user ID. This is an integer optionally followed by a space and an URL. This goes along with - the previous field. The URL is quoted in C style. + the previous field. The URL is quoted in C style. Note that the + origin is stored for a user ID as well as for the entire key. The + latter solves the cases where a key is updated by fingerprint and + and thus there is no way to know which user ID shall be used. + + The meaning of the integer along with the human readable + representation is: + + - 1 (ks) :: Public keyserver. + - 2 (ks-pref) :: Preferred keysrver. + - 3 (dane) :: OpenPGP DANE. + - 4 (wkd) :: Web Key Directory. + - 5 (url) :: Trusted URL. + - 6 (file) :: Trusted file. + - 7 (self) :: Generated by us. *** Field 21 - Comment - This is currently only used in "rev" and "rvs" records to carry - the the comment field of the recocation reason. The value is - quoted in C style. + This is currently only used in "rev", "rvs", ans "pub" records to + carry the comment field of the revocation reason. The value is + quoted in C style. For a "pub" record the comment is preceeded by + a human readable recovation reason followed by a LF; this allows + to show the import entire key revocation (class 0x20) reason + without running a --with-sigs listing. ** Special fields @@ -527,6 +554,12 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: --assert-signer is used. The fingerprint is printed with uppercase hex digits. +*** ASSERT_PUBKEY_ALGO + This is emitted when option --assert-pubkey-algo is used and the + signing algorithms is accepted according to that list if state is + 1 or denied if state is 0. The fingerprint is printed with + uppercase hex digits. + *** SIG_ID This is emitted only for signatures of class 0 or 1 which have been verified okay. The string is a signature id and may be used @@ -557,11 +590,13 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: actual key used for decryption. is the fingerprint of the primary key. is the letter with the ownertrust; this is in general a 'u' which stands for ultimately trusted. -*** DECRYPTION_INFO [] +*** DECRYPTION_INFO [ ] Print information about the symmetric encryption algorithm and the MDC method. This will be emitted even if the decryption fails. - For an AEAD algorithm AEAD_ALGO is not 0. GPGSM currently does - not print such a status. + For an AEAD algorithm AEAD_ALGO is not 0. COMPLERR is set to a + non-zero integer if a compliance check for the cipher failed. + GPGSM currently prints only the first two items and thus they are + marked as optional *** DECRYPTION_FAILED The symmetric decryption failed - one reason could be a wrong @@ -1092,7 +1127,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: gpg-agent. - keyedit.passwd :: Changing the password failed. - nomdc_with_legacy_cipher :: The message was not MDC protected. - Use the command line to lern about a workaround. + Use the command line to learn about a workaround. - random-compliance :: The random number generator or the used version of Libgcrypt do not fulfill the requirements of the current compliance setting. The error code is often @@ -1170,7 +1205,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: . For example "B", "KiB", or "MiB". *** BACKUP_KEY_CREATED - A backup of a key identified by has been writte to + A backup of a key identified by has been written to the file ; is percent-escaped. *** MOUNTPOINT @@ -1254,7 +1289,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB: *** CERTINFO [