diff --git a/NEWS b/NEWS index 48f4fdb3b..232d8beef 100644 --- a/NEWS +++ b/NEWS @@ -1,3873 +1,3913 @@ Noteworthy changes in version 2.3.0 (unreleased) ------------------------------------------------ + Changes also found in 2.2.8: + + * gpg: Decryption of messages not using the MDC mode will now lead + to a hard failure even if a legacy cipher algorithm was used. The + option --ignore-mdc-error can be used to turn this failure into a + warning. Take care: Never use that option unconditionally or + without a prior warning. + + * gpg: The MDC encryption mode is now always used regardless of the + cipher algorithm or any preferences. For testing --rfc2440 can be + used to create a message without an MDC. + + * gpg: Sanitize the diagnostic output of the original file name in + verbose mode. [#4012,CVE-2018-12020] + + * gpg: Detect suspicious multiple plaintext packets in a more + reliable way. [#4000] + + * gpg: Fix the duplicate key signature detection code. [#3994] + + * gpg: The options --no-mdc-warn, --force-mdc, --no-force-mdc, + --disable-mdc and --no-disable-mdc have no more effect. + + * gpg: New command --show-keys. + + * agent: Add DBUS_SESSION_BUS_ADDRESS and a few other envvars to the + list of startup environment variables. [#3947] + + See-also: gnupg-announce/2018q2/000425.html + Changes also found in 2.2.7: * gpg: New option --no-symkey-cache to disable the passphrase cache for symmetrical en- and decryption. * gpg: The ERRSIG status now prints the fingerprint if that is part of the signature. * gpg: Relax emitting of FAILURE status lines * gpg: Add a status flag to "sig" lines printed with --list-sigs. * gpg: Fix "Too many open files" when using --multifile. [#3951] * ssh: Return an error for unknown ssh-agent flags. [#3880] * dirmngr: Fix a regression since 2.1.16 which caused corrupted CRL caches under Windows. [#2448,#3923] * dirmngr: Fix a CNAME problem with pools and TLS. Also use a fixed mapping of keys.gnupg.net to sks-keyservers.net. [#3755] * dirmngr: Try resurrecting dead hosts earlier (from 3 to 1.5 hours). * dirmngr: Fallback to CRL if no default OCSP responder is configured. * dirmngr: Implement CRL fetching via https. Here a redirection to http is explictly allowed. * dirmngr: Make LDAP searching and CRL fetching work under Windows. This stopped working with 2.1. [#3937] * agent,dirmngr: New sub-command "getenv" for "getinfo" to ease debugging. + See-also: gnupg-announce/2018q2/000424.html + Changes also found in 2.2.6: * gpg,gpgsm: New option --request-origin to pretend requests coming from a browser or a remote site. * gpg: Fix race condition on trustdb.gpg updates due to too early released lock. [#3839] * gpg: Emit FAILURE status lines in almost all cases. [#3872] * gpg: Implement --dry-run for --passwd to make checking a key's passphrase straightforward. * gpg: Make sure to only accept a certification capable key for key signatures. [#3844] * gpg: Better user interaction in --card-edit for the factory-reset sub-command. * gpg: Improve changing key attributes in --card-edit by adding an explicit "key-attr" sub-command. [#3781] * gpg: Print the keygrips in the --card-status. * scd: Support KDF DO setup. [#3823] * scd: Fix some issues with PC/SC on Windows. [#3825] * scd: Fix suspend/resume handling in the CCID driver. * agent: Evict cached passphrases also via a timer. [#3829] * agent: Use separate passphrase caches depending on the request origin. [#3858] * ssh: Support signature flags. [#3880] * dirmngr: Handle failures related to missing IPv6 support gracefully. [#3331] * Fix corner cases related to specified home directory with drive letter on Windows. [#3720] * Allow the use of UNC directory names as homedir. [#3818] + See-also: gnupg-announce/2018q2/000421.html + Changes also found in 2.2.5: * gpg: Allow the use of the "cv25519" and "ed25519" short names in addition to the canonical curve names in --batch --gen-key. * gpg: Make sure to print all secret keys with option --list-only and --decrypt. [#3718] * gpg: Fix the use of future-default with --quick-add-key for signing keys. [#3747] * gpg: Select a secret key by checking availability under gpg-agent. [#1967] * gpg: Fix reversed prompt texts for --only-sign-text-ids. [#3787] * gpg,gpgsm: Fix detection of bogus keybox blobs on 32 bit systems. [#3770] * gpgsm: Fix regression since 2.1 in --export-secret-key-raw which got $d mod (q-1)$ wrong. Note that most tools automatically fixup that parameter anyway. * ssh: Fix a regression in getting the client'd PID on *BSD and macOS. * scd: Support the KDF Data Object of the OpenPGP card 3.3. [#3152] * scd: Fix a regression in the internal CCID driver for certain card readers. [#3508] * scd: Fix a problem on NetBSD killing scdaemon on gpg-agent shutdown. [#3778] * dirmngr: Improve returned error description on failure of DNS resolving. [#3756] * wks: Implement command --install-key for gpg-wks-server. * Add option STATIC=1 to the Speedo build system to allow a build with statically linked versions of the core GnuPG libraries. Also use --enable-wks-tools by default by Speedo builds for Unix. + See-also: gnupg-announce/2018q1/000420.html + Changes also found in 2.2.4: * gpg: Change default preferences to prefer SHA512. * gpg: Print a warning when more than 150 MiB are encrypted using a cipher with 64 bit block size. * gpg: Print a warning if the MDC feature has not been used for a message. * gpg: Fix regular expression of domain addresses in trust signatures. [#2923] * agent: New option --auto-expand-secmem to help with high numbers of concurrent connections. Requires libgcrypt 1.8.2 for having an effect. [#3530] * dirmngr: Cache responses of WKD queries. * gpgconf: Add option --status-fd. * wks: Add commands --check and --remove-key to gpg-wks-server. * Increase the backlog parameter of the daemons to 64 and add option --listen-backlog. * New configure option --enable-run-gnupg-user-socket to first try a socket directory which is not removed by systemd at session end. + See-also: gnupg-announce/2017q4/000419.html + Changes also found in 2.2.3: * gpgsm: Fix initial keybox creation on Windows. [#3507] * dirmngr: Fix crash in case of a CRL loading error. [#3510] * Fix the name of the Windows registry key. [Git#4f5afaf1fd] * gpgtar: Fix wrong behaviour of --set-filename. [#3500] * gpg: Silence AKL retrieval messages. [#3504] * agent: Use clock or clock_gettime for calibration. [#3056] * agent: Improve robustness of the shutdown pending state. [Git#7ffedfab89] See-also: gnupg-announce/2017q4/000417.html - Changes also found in 2.2.2: * gpg: Avoid duplicate key imports by concurrently running gpg processes. [#3446] * gpg: Fix creating on-disk subkey with on-card primary key. [#3280] * gpg: Fix validity retrieval for multiple keyrings. [Debian#878812] * gpg: Fix --dry-run and import option show-only for secret keys. * gpg: Print "sec" or "sbb" for secret keys with import option import-show. [#3431] * gpg: Make import less verbose. [#3397] * gpg: Add alias "Key-Grip" for parameter "Keygrip" and new parameter "Subkey-Grip" to unattended key generation. [#3478] * gpg: Improve "factory-reset" command for OpenPGP cards. [#3286] * gpg: Ease switching Gnuk tokens into ECC mode by using the magic keysize value 25519. * gpgsm: Fix --with-colon listing in crt records for fields > 12. * gpgsm: Do not expect X.509 keyids to be unique. [#1644] * agent: Fix stucked Pinentry when using --max-passphrase-days. [#3190] * agent: New option --s2k-count. [#3276 (workaround)] * dirmngr: Do not follow https-to-http redirects. [#3436] * dirmngr: Reduce default LDAP timeout from 100 to 15 seconds. [#3487] * gpgconf: Ignore non-installed components for commands --apply-profile and --apply-defaults. [#3313] * Add configure option --enable-werror. [#2423] See-also: gnupg-announce/2017q4/000416.html Changes also found in 2.2.1: * gpg: Fix formatting of the user id in batch mode key generation if only "name-email" is given. * gpgv: Fix annoying "not suitable for" warnings. * wks: Convey only the newest user id to the provider. This is the case if different names are used with the same addr-spec. * wks: Create a complying user id for provider policy mailbox-only. * wks: Add workaround for posteo.de. * scd: Fix the use of large ECC keys with an OpenPGP card. * dirmngr: Use system provided root certificates if no specific HKP certificates are configured. If build with GNUTLS, this was already the case. + See-also: gnupg-announce/2017q3/000415.html + Release dates of 2.2.x versions: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Version 2.2.1 (2017-09-19) Version 2.2.2 (2017-11-07) Version 2.2.3 (2017-11-20) Version 2.2.4 (2017-12-20) Version 2.2.5 (2018-02-22) Version 2.2.6 (2018-04-09) Version 2.2.7 (2018-05-02) + Version 2.2.8 (2018-06-08) Noteworthy changes in version 2.2.0 (2017-08-28) ------------------------------------------------ This is the new long term stable branch. This branch will only see bug fixes and no new features. * gpg: Reverted change in 2.1.23 so that --no-auto-key-retrieve is again the default. * Fixed a few minor bugs. See-also: gnupg-announce/2017q3/000413.html Noteworthy changes in version 2.1.23 (2017-08-09) ------------------------------------------------- * gpg: "gpg" is now installed as "gpg" and not anymore as "gpg2". If needed, the new configure option --enable-gpg-is-gpg2 can be used to revert this. * gpg: Options --auto-key-retrieve and --auto-key-locate "local,wkd" are now used by default. Note: this enables keyserver and Web Key Directory operators to notice when a signature from a locally non-available key is being verified for the first time or when you intend to encrypt to a mail address without having the key locally. This new behaviour will eventually make key discovery much easier and mostly automatic. Disable this by adding no-auto-key-retrieve auto-key-locate local to your gpg.conf. * agent: Option --no-grab is now the default. The new option --grab allows to revert this. * gpg: New import option "show-only". * gpg: New option --disable-dirmngr to entirely disable network access for gpg. * gpg,gpgsm: Tweaked DE-VS compliance behaviour. * New configure flag --enable-all-tests to run more extensive tests during "make check". * gpgsm: The keygrip is now always printed in colon mode as documented in the man page. * Fixed connection timeout problem under Windows. See-also: gnupg-announce/2017q3/000412.html Noteworthy changes in version 2.1.22 (2017-07-28) ------------------------------------------------- * gpg: Extend command --quick-set-expire to allow for setting the expiration time of subkeys. * gpg: By default try to repair keys during import. New sub-option no-repair-keys for --import-options. * gpg,gpgsm: Improved checking and reporting of DE-VS compliance. * gpg: New options --key-origin and --with-key-origin. Store the time of the last key update from keyservers, WKD, or DANE. * agent: New option --ssh-fingerprint-digest. * dimngr: Lower timeouts on keyserver connection attempts and made it configurable. * dirmngr: Tor will now automatically be detected and used. The option --no-use-tor disables Tor detection. * dirmngr: Now detects a changed /etc/resolv.conf. * agent,dirmngr: Initiate shutdown on removal of the GnuPG home directory. * gpg: Avoid caching passphrase for failed symmetric encryption. * agent: Support for unprotected ssh keys. * dirmngr: Fixed name resolving on systems using only v6 nameservers. * dirmngr: Allow the use of TLS over http proxies. * w32: Change directory of the daemons after startup. * wks: New man pages for client and server. * Many other bug fixes. See-also: gnupg-announce/2017q3/000411.html Noteworthy changes in version 2.1.21 (2017-05-15) ------------------------------------------------- * gpg,gpgsm: Fix corruption of old style keyring.gpg files. This bug was introduced with version 2.1.20. Note that the default pubring.kbx format was not affected. * gpg,dirmngr: Removed the skeleton config file support. The system's standard methods for providing default configuration files should be used instead. * w32: The Windows installer now allows installation of GnuPG without Administrator permissions. * gpg: Fixed import filter property match bug. * scd: Removed Linux support for Cardman 4040 PCMCIA reader. * scd: Fixed some corner case bugs in resume/suspend handling. * Many minor bug fixes and code cleanup. See-also: gnupg-announce/2017q2/000405.html Noteworthy changes in version 2.1.20 (2017-04-03) ------------------------------------------------- * gpg: New properties 'expired', 'revoked', and 'disabled' for the import and export filters. * gpg: New command --quick-set-primary-uid. * gpg: New compliance field for the --with-colon key listing. * gpg: Changed the key parser to generalize the processing of local meta data packets. * gpg: Fixed assertion failure in the TOFU trust model. * gpg: Fixed exporting of zero length user ID packets. * scd: Improved support for multiple readers. * scd: Fixed timeout handling for key generation. * agent: New option --enable-extended-key-format. * dirmngr: Do not add a keyserver to a new dirmngr.conf. Dirmngr uses a default keyserver. * dimngr: Do not treat TLS warning alerts as severe error when building with GNUTLS. * dirmngr: Actually take /etc/hosts in account. * wks: Fixed client problems on Windows. Published keys are now set to world-readable. * tests: Fixed creation of temporary directories. * A socket directory for a non standard GNUGHOME is now created on the fly under /run/user. Thus "gpgconf --create-socketdir" is now optional. The use of "gpgconf --remove-socketdir" to clean up obsolete socket directories is however recommended to avoid cluttering /run/user with useless directories. * Fixed build problems on some platforms. See-also: gnupg-announce/2017q2/000404.html Noteworthy changes in version 2.1.19 (2017-03-01) ------------------------------------------------- * gpg: Print a warning if Tor mode is requested but the Tor daemon is not running. * gpg: New status code DECRYPTION_KEY to print the actual private key used for decryption. * gpgv: New options --log-file and --debug. * gpg-agent: Revamp the prompts to ask for card PINs. * scd: Support for multiple card readers. * scd: Removed option --debug-disable-ticker. Ticker is used only when it is required to watch removal of device/card. * scd: Improved detection of card inserting and removal. * dirmngr: New option --disable-ipv4. * dirmngr: New option --no-use-tor to explicitly disable the use of Tor. * dirmngr: The option --allow-version-check is now required even if the option --use-tor is also used. * dirmngr: Handle a missing nsswitch.conf gracefully. * dirmngr: Avoid PTR lookups for keyserver pools. The are only done for the debug command "keyserver --hosttable". * dirmngr: Rework the internal certificate cache to support classes of certificates. Load system provided certificates on startup. Add options --tls, --no-crl, and --systrust to the "VALIDATE" command. * dirmngr: Add support for the ntbtls library. * wks: Create mails with a "WKS-Phase" header. Fix detection of Draft-2 mode. * The Windows installer is now build with limited TLS support. * Many other bug fixes and new regression tests. See-also: gnupg-announce/2017q1/000402.html Noteworthy changes in version 2.1.18 (2017-01-23) ------------------------------------------------- * gpg: Remove bogus subkey signature while cleaning a key (with export-clean, import-clean, or --edit-key's sub-command clean) * gpg: Allow freezing the clock with --faked-system-time. * gpg: New --export-option flag "backup", new --import-option flag "restore". * gpg-agent: Fixed long delay due to a regression in the progress callback code. * scd: Lots of code cleanup and internal changes. * scd: Improved the internal CCID driver. * dirmngr: Fixed problem with the DNS glue code (removal of the trailing dot in domain names). * dirmngr: Make sure that Tor is actually enabled after changing the conf file and sending SIGHUP or "gpgconf --reload dirmngr". * dirmngr: Fixed Tor access to IPv6 addresses. Note that current versions of Tor may require that the flag "IPv6Traffic" is used with the option "SocksPort" in torrc to actually allow IPv6 traffic. * dirmngr: Fixed HKP for literally given IPv6 addresses. * dirmngr: Enabled reverse DNS lookups via Tor. * dirmngr: Added experimental SRV record lookup for WKD. See commit 88dc3af3d4ae1afe1d5e136bc4c38bc4e7d4cd10 for details. * dirmngr: For HKP use "pgpkey-hkps" and "pgpkey-hkp" in SRV record lookups. Avoid SRV record lookup when a port is explicitly specified. This fixes a regression from the 1.4 and 2.0 behavior. * dirmngr: Gracefully handle a missing /etc/nsswitch.conf. Ignore negation terms (e.g. "[!UNAVAIL=return]" instead of bailing out. * dirmngr: Better debug output for flags "dns" and "network". * dirmngr: On reload mark all known HKP servers alive. * gpgconf: Allow keyword "all" for --launch, --kill, and --reload. * tools: gpg-wks-client now ignores a missing policy file on the server. * Avoid unnecessary ambiguity error message in the option parsing. * Further improvements of the regression test suite. * Fixed building with --disable-libdns configure option. * Fixed a crash running the tests on 32 bit architectures. * Fixed spurious failures on BSD system in the spawn functions. This affected for example gpg-wks-client and gpgconf. See-also: gnupg-announce/2017q1/000401.html Noteworthy changes in version 2.1.17 (2016-12-20) ------------------------------------------------- * gpg: By default new keys expire after 2 years. * gpg: New command --quick-set-expire to conveniently change the expiration date of keys. * gpg: Option and command names have been changed for easier comprehension. The old names are still available as aliases. * gpg: Improved the TOFU trust model. * gpg: New option --default-new-key-algo. * scd: Support OpenPGP card V3 for RSA. * dirmngr: Support for the ADNS library has been removed. Instead William Ahern's Libdns is now source included and used on all platforms. This enables Tor support on all platforms. The new option --standard-resolver can be used to disable this code at runtime. In case of build problems the new configure option --disable-libdns can be used to build without Libdns. * dirmngr: Lazily launch ldap reaper thread. * tools: New options --check and --status-fd for gpg-wks-client. * The UTF-8 byte order mark is now skipped when reading conf files. * Fixed many bugs and regressions. * Major improvements to the test suite. For example it is possible to run the external test suite of GPGME. See-also: gnupg-announce/2016q4/000400.html Noteworthy changes in version 2.1.16 (2016-11-18) ------------------------------------------------- * gpg: New algorithm for selecting the best ranked public key when using a mail address with -r, -R, or --locate-key. * gpg: New option --with-tofu-info to print a new "tfs" record in colon formatted key listings. * gpg: New option --compliance as an alternative way to specify options like --rfc2440, --rfc4880, et al. * gpg: Many changes to the TOFU implementation. * gpg: Improve usability of --quick-gen-key. * gpg: In --verbose mode print a diagnostic when a pinentry is launched. * gpg: Remove code which warns for old versions of gnome-keyring. * gpg: New option --override-session-key-fd. * gpg: Option --output does now work with --verify. * gpgv: New option --output to allow saving the verified data. * gpgv: New option --enable-special-filenames. * agent, dirmngr: New --supervised mode for use by systemd and alike. * agent: By default listen on all available sockets using standard names. * agent: Invoke scdaemon with --homedir. * dirmngr: On Linux now detects the removal of its own socket and terminates. * scd: Support ECC key generation. * scd: Support more card readers. * dirmngr: New option --allow-version-check to download a software version database in the background. * dirmngr: Use system provided CAs if no --hkp-cacert is given. * dirmngr: Use a default keyserver if none is explicitly set * gpgconf: New command --query-swdb to check software versions against an copy of an online database. * gpgconf: Print the socket directory with --list-dirs. * tools: The WKS tools now support draft version -02. * tools: Always build gpg-wks-client and install under libexec. * tools: New option --supported for gpg-wks-client. * The log-file option now accepts a value "socket://" to log to the socket named "S.log" in the standard socket directory. * Provide fake pinentries for use by tests cases of downstream developers. * Fixed many bugs and regressions. * Many changes and improvements for the test suite. See-also: gnupg-announce/2016q4/000398.html Noteworthy changes in version 2.1.15 (2016-08-18) ------------------------------------------------- * gpg: Remove the --tofu-db-format option and support for the split TOFU database. * gpg: Add option --sender to prepare for coming features. * gpg: Add option --input-size-hint to help progress indicators. * gpg: Extend the PROGRESS status line with the counted unit. * gpg: Avoid publishing the GnuPG version by default with --armor. * gpg: Properly ignore legacy keys in the keyring cache. * gpg: Always print fingerprint records in --with-colons mode. * gpg: Make sure that keygrips are printed for each subkey in --with-colons mode. * gpg: New import filter "drop-sig". * gpgsm: Fix a bug in the machine-readable key listing. * gpg,gpgsm: Block signals during keyring updates to limits the effects of a Ctrl-C at the wrong time. * g13: Add command --umount and other fixes for dm-crypt. * agent: Fix regression in SIGTERM handling. * agent: Cleanup of the ssh-agent code. * agent: Allow import of overly long keys. * scd: Fix problems with card removal. * dirmngr: Remove all code for running as a system service. * tools: Make gpg-wks-client conforming to the specs. * tests: Improve the output of the new regression test tool. * tests: Distribute the standalone test runner. * tests: Run each test in a clean environment. * Spelling and grammar fixes. See-also: gnupg-announce/2016q3/000396.html Noteworthy changes in version 2.1.14 (2016-07-14) ------------------------------------------------- * gpg: Removed options --print-dane-records and --print-pka-records. The new export options "export-pka" and "export-dane" can instead be used with the export command. * gpg: New options --import-filter and --export-filter. * gpg: New import options "import-show" and "import-export". * gpg: New option --no-keyring. * gpg: New command --quick-revuid. * gpg: New options -f/--recipient-file and -F/--hidden-recipient-file to directly specify encryption keys. * gpg: New option --mimemode to indicate that the content is a MIME part. Does only enable --textmode right now. * gpg: New option --rfc4880bis to allow experiments with proposed changes to the current OpenPGP specs. * gpg: Fix regression in the "fetch" sub-command of --card-edit. * gpg: Fix regression since 2.1 in option --try-all-secrets. * gpgv: Change default options for extra security. * gpgsm: No more root certificates are installed by default. * agent: "updatestartuptty" does now affect more environment variables. * scd: The option --homedir does now work with scdaemon. * scd: Support some more GEMPlus card readers. * gpgtar: Fix handling of '-' as file name. * gpgtar: New commands --create and --extract. * gpgconf: Tweak for --list-dirs to better support shell scripts. * tools: Add programs gpg-wks-client and gpg-wks-server to implement a Web Key Service. The configure option --enable-wks-tools is required to build them; they should be considered Beta software. * tests: Complete rework of the openpgp part of the test suite. The test scripts have been changed from Bourne shell scripts to Scheme programs. A customized scheme interpreter (gpgscm) is included. This change was triggered by the need to run the test suite on non-Unix platforms. * The rendering of the man pages has been improved. See-also: gnupg-announce/2016q3/000393.html Noteworthy changes in version 2.1.13 (2016-06-16) ------------------------------------------------- * gpg: New command --quick-addkey. Extend the --quick-gen-key command. * gpg: New --keyid-format "none" which is now also the default. * gpg: New option --with-subkey-fingerprint. * gpg: Include Signer's UID subpacket in signatures if the secret key has been specified using a mail address and the new option --disable-signer-uid is not used. * gpg: Allow unattended deletion of a secret key. * gpg: Allow export of non-passphrase protected secret keys. * gpg: New status lines KEY_CONSIDERED and NOTATION_FLAGS. * gpg: Change status line TOFU_STATS_LONG to use '~' as a non-breaking-space character. * gpg: Speedup key listings in Tofu mode. * gpg: Make sure that the current and total values of a PROGRESS status line are small enough. * gpgsm: Allow the use of AES192 and SERPENT ciphers. * dirmngr: Adjust WKD lookup to current specs. * dirmngr: Fallback to LDAP v3 if v2 is is not supported. * gpgconf: New commands --create-socketdir and --remove-socketdir, new option --homedir. * If a /run/user/$UID directory exists, that directory is now used for IPC sockets instead of the GNUPGHOME directory. This fixes problems with NFS and too long socket names and thus avoids the need for redirection files. * The Speedo build systems now uses the new versions.gnupg.org server to retrieve the default package versions. * Fix detection of libusb on FreeBSD. * Speedup fd closing after a fork. See-also: gnupg-announce/2016q2/000390.html Noteworthy changes in version 2.1.12 (2016-05-04) ------------------------------------------------- * gpg: New --edit-key sub-command "change-usage" for testing purposes. * gpg: Out of order key-signatures are now systematically detected and fixed by --edit-key. * gpg: Improved detection of non-armored messages. * gpg: Removed the extra prompt needed to create Curve25519 keys. * gpg: Improved user ID selection for --quick-sign-key. * gpg: Use the root CAs provided by the system with --fetch-key. * gpg: Add support for the experimental Web Key Directory key location service. * gpg: Improve formatting of Tofu messages and emit new Tofu specific status lines. * gpgsm: Add option --pinentry-mode to support a loopback pinentry. * gpgsm: A new pubring.kbx is now created with the header blob so that gpg can detect that the keybox format needs to be used. * agent: Add read support for the new private key protection format openpgp-s2k-ocb-aes. * agent: Add read support for the new extended private key format. * agent: Default to --allow-loopback-pinentry and add option --no-allow-loopback-pinentry. * scd: Changed to use the new libusb 1.0 API for the internal CCID driver. * dirmngr: The dirmngr-client does now auto-detect the PEM format. * g13: Add experimental support for dm-crypt. * w32: Tofu support is now available with the Speedo build method. * w32: Removed the need for libiconv.dll. * The man pages for gpg and gpgv are now installed under the correct name (gpg2 or gpg - depending on a configure option). * Lots of internal cleanups and bug fixes. See-also: gnupg-announce/2016q2/000387.html Noteworthy changes in version 2.1.11 (2016-01-26) ------------------------------------------------- * gpg: New command --export-ssh-key to replace the gpgkey2ssh tool. * gpg: Allow to generate mail address only keys with --gen-key. * gpg: "--list-options show-usage" is now the default. * gpg: Make lookup of DNS CERT records holding an URL work. * gpg: Emit PROGRESS status lines during key generation. * gpg: Don't check for ambigious or non-matching key specification in the config file or given to --encrypt-to. This feature will return in 2.3.x. * gpg: Lock keybox files while updating them. * gpg: Solve rare error on Windows during keyring and Keybox updates. * gpg: Fix possible keyring corruption. (bug#2193) * gpg: Fix regression of "bkuptocard" sub-command in --edit-key and remove "checkbkupkey" sub-command introduced with 2.1. (bug#2169) * gpg: Fix internal error in gpgv when using default keyid-format. * gpg: Fix --auto-key-retrieve to work with dirmngr.conf configured keyservers. (bug#2147). * agent: New option --pinentry-timeout. * scd: Improve unplugging of USB readers under Windows. * scd: Fix regression for generating RSA keys on card. * dirmmgr: All configured keyservers are now searched. * dirmngr: Install CA certificate for hkps.pool.sks-keyservers.net. Use this certiticate even if --hkp-cacert is not used. * gpgtar: Add actual encryption code. gpgtar does now fully replace gpg-zip. * gpgtar: Fix filename encoding problem on Windows. * Print a warning if a GnuPG component is using an older version of gpg-agent, dirmngr, or scdaemon. See-also: gnupg-announce/2016q1/000383.html Noteworthy changes in version 2.1.10 (2015-12-04) ------------------------------------------------- * gpg: New trust models "tofu" and "tofu+pgp". * gpg: New command --tofu-policy. New options --tofu-default-policy and --tofu-db-format. * gpg: New option --weak-digest to specify hash algorithms which should be considered weak. * gpg: Allow the use of multiple --default-key options; take the last available key. * gpg: New option --encrypt-to-default-key. * gpg: New option --unwrap to only strip the encryption layer. * gpg: New option --only-sign-text-ids to exclude photo IDs from key signing. * gpg: Check for ambigious or non-matching key specification in the config file or given to --encrypt-to. * gpg: Show the used card reader with --card-status. * gpg: Print export statistics and an EXPORTED status line. * gpg: Allow selecting subkeys by keyid in --edit-key. * gpg: Allow updating the expiration time of multiple subkeys at once. * dirmngr: New option --use-tor. For full support this requires libassuan version 2.4.2 and a patched version of libadns (e.g. adns-1.4-g10-7 as used by the standard Windows installer). * dirmngr: New option --nameserver to specify the nameserver used in Tor mode. * dirmngr: Keyservers may again be specified by IP address. * dirmngr: Fixed problems in resolving keyserver pools. * dirmngr: Fixed handling of premature termination of TLS streams so that large numbers of keys can be refreshed via hkps. * gpg: Fixed a regression in --locate-key [since 2.1.9]. * gpg: Fixed another bug for keyrings with legacy keys. * gpgsm: Allow combinations of usage flags in --gen-key. * Make tilde expansion work with most options. * Many other cleanups and bug fixes. See-also: gnupg-announce/2015q4/000381.html Noteworthy changes in version 2.1.9 (2015-10-09) ------------------------------------------------ * gpg: Allow fetching keys via OpenPGP DANE (--auto-key-locate). New option --print-dane-records. [Update: --print-dane-records replaced in 2.1.4.] * gpg: Fix for a problem with PGP-2 keys in a keyring. * gpg: Fail with an error instead of a warning if a modern cipher algorithm is used without a MDC. * agent: New option --pinentry-invisible-char. * agent: Always do a RSA signature verification after creation. * agent: Fix a regression in ssh-add-ing Ed25519 keys. * agent: Fix ssh fingerprint computation for nistp384 and EdDSA. * agent: Fix crash during passphrase entry on some platforms. * scd: Change timeout to fix problems with some 2.1 cards. * dirmngr: Displayed name is now Key Acquirer. * dirmngr: Add option --keyserver. Deprecate that option for gpg. Install a dirmngr.conf file from a skeleton for new installations. See-also: gnupg-announce/2015q4/000380.html Noteworthy changes in version 2.1.8 (2015-09-10) ------------------------------------------------ * gpg: Sending very large keys to the keyservers works again. * gpg: Validity strings in key listings are now again translatable. * gpg: Emit FAILURE status lines to help GPGME. * gpg: Does not anymore link to Libksba to reduce dependencies. * gpgsm: Export of secret keys via Assuan is now possible. * agent: Raise the maximum passphrase length from 100 to 255 bytes. * agent: Fix regression using EdDSA keys with ssh. * Does not anymore use a build timestamp by default. * The fallback encoding for broken locale settings changed from Latin-1 to UTF-8. * Many code cleanups and improved internal documentation. * Various minor bug fixes. See-also: gnupg-announce/2015q3/000379.html Noteworthy changes in version 2.1.7 (2015-08-11) ------------------------------------------------ * gpg: Support encryption with Curve25519 if Libgcrypt 1.7 is used. * gpg: In the --edit-key menu: Removed the need for "toggle", changed how secret keys are indicated, new commands "fpr *" and "grip". * gpg: More fixes related to legacy keys in a keyring. * gpgv: Does now also work with a "trustedkeys.kbx" file. * scd: Support some feature from the OpenPGP card 3.0 specs. * scd: Improved ECC support * agent: New option --force for the DELETE_KEY command. * w32: Look for the Pinentry at more places. * Dropped deprecated gpgsm-gencert.sh * Various other bug fixes. See-also: gnupg-announce/2015q3/000371.html Noteworthy changes in version 2.1.6 (2015-07-01) ------------------------------------------------ * agent: New option --verify for the PASSWD command. * gpgsm: Add command option "offline" as an alternative to --disable-dirmngr. * gpg: Do not prompt multiple times for a password in pinentry loopback mode. * Allow the use of debug category names with --debug. * Using gpg-agent and gpg/gpgsm with different locales will now show the correct translations in Pinentry. * gpg: Improve speed of --list-sigs and --check-sigs. * gpg: Make --list-options show-sig-subpackets work again. * gpg: Fix an export problem for old keyrings with PGP-2 keys. * scd: Support PIN-pads on more readers. * dirmngr: Properly cleanup zombie LDAP helper processes and avoid hangs on dirmngr shutdown. * Various other bug fixes. See-also: gnupg-announce/2015q3/000370.html Noteworthy changes in version 2.1.5 (2015-06-11) ------------------------------------------------ * Support for an external passphrase cache. * Support for the forthcoming version 3 OpenPGP smartcard. * Manuals now show the actual used file names. * Prepared for improved integration with Emacs. * Code cleanups and minor bug fixes. See-also: gnupg-announce/2015q2/000369.html Noteworthy changes in version 2.1.4 (2015-05-12) ------------------------------------------------ * gpg: Add command --quick-adduid to non-interactively add a new user id to an existing key. * gpg: Do no enable honor-keyserver-url by default. Make it work if enabled. * gpg: Display the serial number in the --card-status output again. * agent: Support for external password managers. Add option --no-allow-external-cache. * scdaemon: Improved handling of extended APDUs. * Make HTTP proxies work again. * All network access including DNS as been moved to Dirmngr. * Allow building without LDAP support. * Fixed lots of smaller bugs. See-also: gnupg-announce/2015q2/000366.html Noteworthy changes in version 2.1.3 (2015-04-11) ------------------------------------------------ * gpg: LDAP keyservers are now supported by 2.1. * gpg: New option --with-icao-spelling. * gpg: New option --print-pka-records. Changed the PKA method to use CERT records and hashed names. [Update: --print-pka-records replaced in 2.1.14.] * gpg: New command --list-gcrypt-config. New parameter "curve" for --list-config. * gpg: Print a NEWSIG status line like gpgsm always did. * gpg: Print MPI values with --list-packets and --verbose. * gpg: Write correct MPI lengths with ECC keys. * gpg: Skip legacy PGP-2 keys while searching. * gpg: Improved searching for mail addresses when using a keybox. * gpgsm: Changed default algos to AES-128 and SHA-256. * gpgtar: Fixed extracting files with sizes of a multiple of 512. * dirmngr: Fixed SNI handling for hkps pools. * dirmngr: extra-certs and trusted-certs are now always loaded from the sysconfig dir instead of the homedir. * Fixed possible problems due to compiler optimization, two minor regressions, and other bugs. See-also: gnupg-announce/2015q2/000365.html Noteworthy changes in version 2.1.2 (2015-02-11) ------------------------------------------------ * gpg: The parameter 'Passphrase' for batch key generation works again. * gpg: Using a passphrase option in batch mode now has the expected effect on --quick-gen-key. * gpg: Improved reporting of unsupported PGP-2 keys. * gpg: Added support for algo names when generating keys using --command-fd. * gpg: Fixed DoS based on bogus and overlong key packets. * agent: When setting --default-cache-ttl the value for --max-cache-ttl is adjusted to be not lower than the former. * agent: Fixed problems with the new --extra-socket. * agent: Made --allow-loopback-pinentry changeable with gpgconf. * agent: Fixed importing of unprotected openpgp keys. * agent: Now tries to use a fallback pinentry if the standard pinentry is not installed. * scd: Added support for ECDH. * Fixed several bugs related to bogus keyrings and improved some other code. See-also: gnupg-announce/2015q1/000361.html Noteworthy changes in version 2.1.1 (2014-12-16) ------------------------------------------------ * gpg: Detect faulty use of --verify on detached signatures. * gpg: New import option "keep-ownertrust". * gpg: New sub-command "factory-reset" for --card-edit. * gpg: A stub key for smartcards is now created by --card-status. * gpg: Fixed regression in --refresh-keys. * gpg: Fixed regresion in %g and %p codes for --sig-notation. * gpg: Fixed best matching hash algo detection for ECDSA and EdDSA. * gpg: Improved perceived speed of secret key listisngs. * gpg: Print number of skipped PGP-2 keys on import. * gpg: Removed the option aliases --throw-keyid and --notation-data; use --throw-keyids and --set-notation instead. * gpg: New import option "keep-ownertrust". * gpg: Skip too large keys during import. * gpg,gpgsm: New option --no-autostart to avoid starting gpg-agent or dirmngr. * gpg-agent: New option --extra-socket to provide a restricted command set for use with remote clients. * gpgconf --kill does not anymore start a service only to kill it. * gpg-pconnect-agent: Add convenience option --uiserver. * Fixed keyserver access for Windows. * Fixed build problems on Mac OS X * The Windows installer does now install development files * More translations (but most of them are not complete). * To support remotely mounted home directories, the IPC sockets may now be redirected. This feature requires Libassuan 2.2.0. * Improved portability and the usual bunch of bug fixes. See-also: gnupg-announce/2014q4/000360.html Noteworthy changes in version 2.1.0 (2014-11-06) ------------------------------------------------ This release introduces a lot of changes. Most of them are internal and thus not user visible. However, some long standing behavior has slightly changed and it is strongly suggested that an existing "~/.gnupg" directory is backed up before this version is used. A verbose description of the major new features and changes can be found in the file doc/whats-new-in-2.1.txt. * gpg: All support for v3 (PGP 2) keys has been dropped. All signatures are now created as v4 signatures. v3 keys will be removed from the keyring. * gpg: With pinentry-0.9.0 the passphrase "enter again" prompt shows up in the same window as the "new passphrase" prompt. * gpg: Allow importing keys with duplicated long key ids. * dirmngr: May now be build without support for LDAP. * For a complete list of changes see the lists of changes for the 2.1.0 beta versions below. Note that all relevant fixes from versions 2.0.14 to 2.0.26 are also applied to this version. [Noteworthy changes in version 2.1.0-beta864 (2014-10-03)] * gpg: Removed the GPG_AGENT_INFO related code. GnuPG does now always use a fixed socket name in its home directory. * gpg: Renamed --gen-key to --full-gen-key and re-added a --gen-key command with less choices. * gpg: Use SHA-256 for all signature types also on RSA keys. * gpg: Default keyring is now created with a .kbx suffix. * gpg: Add a shortcut to the key capabilies menu (e.g. "=e" sets the encryption capabilities). * gpg: Fixed obsolete options parsing. * Further improvements for the alternative speedo build system. [Noteworthy changes in version 2.1.0-beta834 (2014-09-18)] * gpg: Improved passphrase caching. * gpg: Switched to algorithm number 22 for EdDSA. * gpg: Removed CAST5 from the default preferences. * gpg: Order SHA-1 last in the hash preferences. * gpg: Changed default cipher for --symmetric to AES-128. * gpg: Fixed export of ECC keys and import of EdDSA keys. * dirmngr: Fixed the KS_FETCH command. * The speedo build system now downloads related packages and works for non-Windows platforms. [Noteworthy changes in version 2.1.0-beta783 (2014-08-14)] * gpg: Add command --quick-gen-key. * gpg: Make --quick-sign-key promote local key signatures. * gpg: Added "show-usage" sub-option to --list-options. * gpg: Screen keyserver responses to avoid importing unwanted keys from rogue servers. * gpg: Removed the option --pgp2 and --rfc1991 and the ability to create PGP-2 compatible messages. * gpg: Removed options --compress-keys and --compress-sigs. * gpg: Cap attribute packets at 16MB. * gpg: Improved output of --list-packets. * gpg: Make with-colons output of --search-keys work again. * gpgsm: Auto-create the ".gnupg" directory like gpg does. * agent: Fold new passphrase warning prompts into one. * scdaemon: Add support for the Smartcard-HSM card. * scdaemon: Remove the use of the pcsc-wrapper. [Noteworthy changes in version 2.1.0-beta751 (2014-07-03)] * gpg: Create revocation certificates during key generation. * gpg: Create exported secret keys and revocation certifciates with mode 0700 * gpg: The validity of user ids is now shown by default. To revert this add "list-options no-show-uid-validity" to gpg.conf. * gpg: Make export of secret keys work again. * gpg: The output of --list-packets does now print the offset of the packet and information about the packet header. * gpg: Avoid DoS due to garbled compressed data packets. [CVE-2014-4617] * gpg: Print more specific reason codes with the INV_RECP status. * gpg: Cap RSA and Elgamal keysize at 4096 bit also for unattended key generation. * scdaemon: Support reader Gemalto IDBridge CT30 and pinpad of SCT cyberJack go. * The speedo build system has been improved. It is now also possible to build a partly working installer for Windows. [Noteworthy changes in version 2.1.0-beta442 (2014-06-05)] * gpg: Changed the format of key listings. To revert to the old format the option --legacy-list-mode is available. * gpg: Add experimental signature support using curve Ed25519 and with a patched Libgcrypt also encryption support with Curve25519. [Update: this encryption support has been removed from 2.1.0 until we have agreed on a suitable format.] * gpg: Allow use of Brainpool curves. * gpg: Accepts a space separated fingerprint as user ID. This allows to copy and paste the fingerprint from the key listing. * gpg: The hash algorithm is now printed for signature records in key listings. * gpg: Reject signatures made using the MD5 hash algorithm unless the new option --allow-weak-digest-algos or --pgp2 are given. * gpg: Print a warning if the Gnome-Keyring-Daemon intercepts the communication with the gpg-agent. * gpg: New option --pinentry-mode. * gpg: Fixed decryption using an OpenPGP card. * gpg: Fixed bug with deeply nested compressed packets. * gpg: Only the major version number is by default included in the armored output. * gpg: Do not create a trustdb file if --trust-model=always is used. * gpg: Protect against rogue keyservers sending secret keys. * gpg: The format of the fallback key listing ("gpg KEYFILE") is now more aligned to the regular key listing ("gpg -k"). * gpg: The option--show-session-key prints its output now before the decryption of the bulk message starts. * gpg: New %U expando for the photo viewer. * gpg,gpgsm: New option --with-secret. * gpgsm: By default the users are now asked via the Pinentry whether they trust an X.509 root key. To prohibit interactive marking of such keys, the new option --no-allow-mark-trusted may be used. * gpgsm: New commands to export a secret RSA key in PKCS#1 or PKCS#8 format. * gpgsm: Improved handling of re-issued CA certificates. * agent: The included ssh agent does now support ECDSA keys. * agent: New option --enable-putty-support to allow gpg-agent on Windows to act as a Pageant replacement with full smartcard support. * scdaemon: New option --enable-pinpad-varlen. * scdaemon: Various fixes for pinpad equipped card readers. * scdaemon: Rename option --disable-pinpad (was --disable-keypad). * scdaemon: Better support fo CCID readers. Now, internal CCID driver supports readers with no auto configuration feature. * dirmngr: Removed support for the original HKP keyserver which is not anymore used by any site. * dirmngr: Improved support for keyserver pools. * tools: New option --dirmngr for gpg-connect-agent. * The GNU Pth library has been replaced by the new nPth library. * Support installation as portable application under Windows. * All kind of other improvements - see the git log. [Noteworthy changes in version 2.1.0beta3 (2011-12-20)] * gpg: Fixed regression in the secret key export function. * gpg: Allow generation of card keys up to 4096 bit. * gpgsm: Preliminary support for the validation model "steed". * gpgsm: Improved certificate creation. * agent: Support the SSH confirm flag. * agent: New option to select a passphrase mode. The loopback mode may be used to bypass Pinentry. * agent: The Assuan commands KILLAGENT and KILLSCD are working again. * scdaemon: Does not anymore block after changing a card (regression fix). * tools: gpg-connect-agent does now proberly display the help output for "SCD HELP" commands. [Noteworthy changes in version 2.1.0beta2 (2011-03-08)] * gpg: ECC support as described by draft-jivsov-openpgp-ecc-06.txt [Update: now known as RFC-6637]. * gpg: Print "AES128" instead of "AES". This change introduces a little incompatibility for tools using "gpg --list-config". We hope that these tools are written robust enough to accept this new algorithm name as well. * gpgsm: New feature to create certificates from a parameter file. Add prompt to the --gen-key UI to create self-signed certificates. * agent: TMPDIR is now also honored when creating a socket using the --no-standard-socket option and with symcryptrun's temp files. * scdaemon: Fixed a bug where scdaemon sends a signal to gpg-agent running in non-daemon mode. * dirmngr: Fixed CRL loading under W32 (bug#1010). * Dirmngr has taken over the function of the keyserver helpers. Thus we now have a specified direct interface to keyservers via Dirmngr. LDAP, DNS and mail backends are not yet implemented. * Fixed TTY management for pinentries and session variable update problem. [Noteworthy changes in version 2.1.0beta1 (2010-10-26)] * gpg: secring.gpg is not anymore used but all secret key operations are delegated to gpg-agent. The import command moves secret keys to the agent. * gpg: The OpenPGP import command is now able to merge secret keys. * gpg: Encrypted OpenPGP messages with trailing data (e.g. other OpenPGP packets) are now correctly parsed. * gpg: Given sufficient permissions Dirmngr is started automagically. * gpg: Fixed output of "gpgconf --check-options". * gpg: Removed options --export-options(export-secret-subkey-passwd) and --simple-sk-checksum. * gpg: New options --try-secret-key. * gpg: Support DNS lookups for SRV, PKA and CERT on W32. * gpgsm: The --audit-log feature is now more complete. * gpgsm: The default for --include-cert is now to include all certificates in the chain except for the root certificate. * gpgsm: New option --ignore-cert-extension. * g13: The G13 tool for disk encryption key management has been added. * agent: If the agent's --use-standard-socket option is active, all tools try to start and daemonize the agent on the fly. In the past this was only supported on W32; on non-W32 systems the new configure option --disable-standard-socket may now be used to disable this new default. * agent: New and changed passphrases are now created with an iteration count requiring about 100ms of CPU work. * dirmngr: Dirmngr is now a part of this package. It is now also expected to run as a system service and the configuration directories are changed to the GnuPG name space. [Update: 2.1.0 starts dirmngr on demand as user daemon.] * Support for Windows CE. [Update: This has not been tested for the 2.1.0 release] * Numerical values may now be used as an alternative to the debug-level keywords. See-also: gnupg-announce/2014q4/000358.html Version 2.0.28 (2015-06-02) Version 2.0.27 (2015-02-18) Version 2.0.26 (2014-08-12) Version 2.0.25 (2014-06-30) Version 2.0.24 (2014-06-24) Version 2.0.23 (2014-06-03) Version 2.0.22 (2013-10-04) Version 2.0.21 (2013-08-19) Version 2.0.20 (2013-05-10) Version 2.0.19 (2012-03-27) Version 2.0.18 (2011-08-04) Version 2.0.17 (2011-01-13) Version 2.0.16 (2010-07-19) Version 2.0.15 (2010-03-09) Version 2.0.14 (2009-12-21) Noteworthy changes in version 2.0.13 (2009-09-04) ------------------------------------------------- * GPG now generates 2048 bit RSA keys by default. The default hash algorithm preferences has changed to prefer SHA-256 over SHA-1. 2048 bit DSA keys are now generated to use a 256 bit hash algorithm * The envvars XMODIFIERS, GTK_IM_MODULE and QT_IM_MODULE are now passed to the Pinentry to make SCIM work. * The GPGSM command --gen-key features a --batch mode and implements all features of gpgsm-gencert.sh in standard mode. * New option --re-import for GPGSM's IMPORT server command. * Enhanced writing of existing keys to OpenPGP v2 cards. * Add hack to the internal CCID driver to allow the use of some Omnikey based card readers with 2048 bit keys. * GPG now repeatly asks the user to insert the requested OpenPGP card. This can be disabled with --limit-card-insert-tries=1. * Minor bug fixes. See-also: gnupg-announce/2009q3/000294.html Noteworthy changes in version 2.0.12 (2009-06-17) ------------------------------------------------- * GPGSM now always lists ephemeral certificates if specified by fingerprint or keygrip. * New command "KEYINFO" for GPG_AGENT. GPGSM now also returns information about smartcards. * Made sure not to leak file descriptors if running gpg-agent with a command. Restore the signal mask to solve a problem in Mono. * Changed order of the confirmation questions for root certificates and store negative answers in trustlist.txt. * Better synchronization of concurrent smartcard sessions. * Support 2048 bit OpenPGP cards. * Support Telesec Netkey 3 cards. * The gpg-protect-tool now uses gpg-agent via libassuan. Under Windows the Pinentry will now be put into the foreground. * Changed code to avoid a possible Mac OS X system freeze. See-also: gnupg-announce/2009q2/000288.html Noteworthy changes in version 2.0.11 (2009-03-03) ------------------------------------------------- * Fixed a problem in SCDAEMON which caused unexpected card resets. * SCDAEMON is now aware of the Geldkarte. * The SCDAEMON option --allow-admin is now used by default. * GPGCONF now restarts SCdaemon if necessary. * The default cipher algorithm in GPGSM is now again 3DES. This is due to interoperability problems with Outlook 2003 which still can't cope with AES. See-also: gnupg-announce/2009q1/000287.html Noteworthy changes in version 2.0.10 (2009-01-12) ------------------------------------------------- * [gpg] New keyserver helper gpg2keys_kdns as generic DNS CERT lookup. Run with --help for a short description. Requires the ADNS library. * [gpg] New mechanisms "local" and "nodefault" for --auto-key-locate. Fixed a few problems with this option. * [gpg] New command --locate-keys. * [gpg] New options --with-sig-list and --with-sig-check. * [gpg] The option "-sat" is no longer an alias for --clearsign. * [gpg] The option --fixed-list-mode is now implicitly used and obsolete. * [gpg] New control statement %ask-passphrase for the unattended key generation. * [gpg] The algorithm to compute the SIG_ID status has been changed. * [gpgsm] Now uses AES by default. * [gpgsm] Made --output option work with --export-secret-key-p12. * [gpg-agent] Terminate process if the own listening socket is not anymore served by ourself. * [scdaemon] Made it more robust on W32. * [gpg-connect-agent] Accept commands given as command line arguments. * [w32] Initialized the socket subsystem for all keyserver helpers. * [w32] The sysconf directory has been moved from a subdirectory of the installation directory to %CSIDL_COMMON_APPDATA%/GNU/etc/gnupg. * [w32] The gnupg2.nls directory is not anymore used. The standard locale directory is now used. * [w32] Fixed a race condition between gpg and gpgsm in the use of temporary file names. * The gpg-preset-passphrase mechanism works again. An arbitrary string may now be used for a custom cache ID. * Admin PINs are cached again (bug in 2.0.9). * Support for version 2 OpenPGP cards. * Libgcrypt 1.4 is now required. See-also: gnupg-announce/2009q1/000284.html Noteworthy changes in version 2.0.9 (2008-03-26) ------------------------------------------------ * Gpgsm always tries to locate missing certificates from a running Dirmngr's cache. * Tweaks for Windows. * The Admin PIN for OpenPGP cards may now be entered with the pinpad. * Improved certificate chain construction. * Extended the PKITS framework. * Fixed a bug in the ambigious name detection. * Fixed possible memory corruption while importing OpenPGP keys (bug introduced with 2.0.8). [CVE-2008-1530] * Minor bug fixes. Noteworthy changes in version 2.0.8 (2007-12-20) ------------------------------------------------ * Enhanced gpg-connect-agent with a small scripting language. * New option --list-config for gpgconf. * Fixed a crash in gpgconf. * Gpg-agent now supports the passphrase quality bar of the latest Pinentry. * The envvars XAUTHORITY and PINENTRY_USER_DATA are now passed to the Pinentry. * Fixed the auto creation of the key stub for smartcards. * Fixed a rare bug in decryption using the OpenPGP card. * Creating DSA2 keys is now possible. * New option --extra-digest-algo for gpgsm to allow verification of broken signatures. * Allow encryption with legacy Elgamal sign+encrypt keys with option --rfc2440. * Windows is now a supported platform. * Made sure that under Windows the file permissions of the socket are taken into account. This required a change of our socket emulation code and changed the IPC protocol under Windows. See-also: gnupg-announce/2007q4/000267.html Noteworthy changes in version 2.0.7 (2007-09-10) ------------------------------------------------ * Fixed encryption problem if duplicate certificates are in the keybox. * Made it work on Windows Vista. Note that the entire Windows port is still considered Beta. * Add new options min-passphrase-nonalpha, check-passphrase-pattern, enforce-passphrase-constraints and max-passphrase-days to gpg-agent. * Add command --check-components to gpgconf. Gpgconf now uses the installed versions of the programs and does not anymore search via PATH for them. See-also: gnupg-announce/2007q3/000259.html Noteworthy changes in version 2.0.6 (2007-08-16) ------------------------------------------------ * GPGSM does now grok --default-key. * GPGCONF is now aware of --default-key and --encrypt-to. * GPGSM does again correctly print the serial number as well the the various keyids. This was broken since 2.0.4. * New option --validation-model and support for the chain-model. * Improved Windows support. See-also: gnupg-announce/2007q3/000258.html Noteworthy changes in version 2.0.5 (2007-07-05) ------------------------------------------------ * Switched license to GPLv3. * Basic support for Windows. Run "./autogen.sh --build-w32" to build it. As usual the mingw cross compiling toolchain is required. * Fixed bug when using the --p12-charset without --armor. * The command --gen-key may now be used instead of the gpgsm-gencert.sh script. * Changed key generation to reveal less information about the machine. Bug fixes for gpg2's card key generation. See-also: gnupg-announce/2007q3/000255.html Noteworthy changes in version 2.0.4 (2007-05-09) ------------------------------------------------ * The server mode key listing commands are now also working for systems without the funopen/fopencookie API. * PKCS#12 import now tries several encodings in case the passphrase was not utf-8 encoded. New option --p12-charset for gpgsm. * Improved the libgcrypt logging support in all modules. See-also: gnupg-announce/2007q2/000254.html Noteworthy changes in version 2.0.3 (2007-03-08) ------------------------------------------------ * By default, do not allow processing multiple plaintexts in a single stream. Many programs that called GnuPG were assuming that GnuPG did not permit this, and were thus not using the plaintext boundary status tags that GnuPG provides. This change makes GnuPG reject such messages by default which makes those programs safe again. --allow-multiple-messages returns to the old behavior. [CVE-2007-1263]. * New --verify-option show-primary-uid-only. * gpgconf may now reads a global configuration file to select which options are changeable by a frontend. The new applygnupgdefaults tool may be used by an admin to set default options for all users. * The PIN pad of the Cherry XX44 keyboard is now supported. The DINSIG and the NKS applications are now also aware of PIN pads. See-also: gnupg-announce/2007q1/000252.html Noteworthy changes in version 2.0.2 (2007-01-31) ------------------------------------------------ * Fixed a serious and exploitable bug in processing encrypted packages. [CVE-2006-6235]. * Added --passphrase-repeat to set the number of times GPG will prompt for a new passphrase to be repeated. This is useful to help memorize a new passphrase. The default is 1 repetition. * Using a PIN pad does now also work for the signing key. * A warning is displayed by gpg-agent if a new passphrase is too short. New option --min-passphrase-len defaults to 8. * The status code BEGIN_SIGNING now shows the used hash algorithms. See-also: gnupg-announce/2007q1/000249.html Noteworthy changes in version 2.0.1 (2006-11-28) ------------------------------------------------ * Experimental support for the PIN pads of the SPR 532 and the Kaan Advanced card readers. Add "disable-keypad" scdaemon.conf if you don't want it. Does currently only work for the OpenPGP card and its authentication and decrypt keys. * Fixed build problems on some some platforms and crashes on amd64. * Fixed a buffer overflow in gpg2. [bug#728,CVE-2006-6169] See-also: gnupg-announce/2006q4/000242.html Noteworthy changes in version 2.0.0 (2006-11-11) ------------------------------------------------ * First stable version of a GnuPG integrating OpenPGP and S/MIME. See-also: gnupg-announce/2006q4/000239.html Noteworthy changes in version 1.9.95 (2006-11-06) ------------------------------------------------- * Minor bug fixes. Noteworthy changes in version 1.9.94 (2006-10-24) ------------------------------------------------- * Keys for gpgsm may now be specified using a keygrip. A keygrip is indicated by a prefixing it with an ampersand. * gpgconf now supports switching the CMS cipher algo (e.g. to AES). * New command --gpgconf-test for all major tools. This may be used to check whether the configuration file is sane. Noteworthy changes in version 1.9.93 (2006-10-18) ------------------------------------------------- * In --with-validation mode gpgsm will now also ask whether a root certificate should be trusted. * Link to Pth only if really necessary. * Fixed a pubring corruption bug in gpg2 occurring when importing signatures or keys with insane lengths. * Fixed v3 keyID calculation bug in gpg2. * More tweaks for certificates without extensions. Noteworthy changes in version 1.9.92 (2006-10-11) ------------------------------------------------- * Bug fixes. See-also: gnupg-announce/2006q4/000236.html Noteworthy changes in version 1.9.91 (2006-10-04) ------------------------------------------------- * New "relax" flag for trustlist.txt to allow root CA certificates without BasicContraints. * [gpg2] Removed the -k PGP 2 compatibility hack. -k is now an alias for --list-keys. * [gpg2] Print a warning if "-sat" is used instead of "--clearsign". Noteworthy changes in version 1.9.90 (2006-09-25) ------------------------------------------------- * Made readline work for gpg. * Cleanups und minor bug fixes. * Included translations from gnupg 1.4.5. Noteworthy changes in version 1.9.23 (2006-09-18) ------------------------------------------------- * Regular man pages for most tools are now build directly from the Texinfo source. * The gpg code from 1.4.5 has been fully merged into this release. The configure option --enable-gpg is still required to build this gpg part. For production use of OpenPGP the gpg version 1.4.5 is still recommended. Note, that gpg will be installed under the name gpg2 to allow coexisting with an 1.4.x gpg. * API change in gpg-agent's pkdecrypt command. Thus an older gpgsm may not be used with the current gpg-agent. * The scdaemon will now call a script on reader status changes. * gpgsm now allows file descriptor passing for "INPUT", "OUTPUT" and "MESSAGE". * The gpgsm server may now output a key listing to the output file handle. This needs to be enabled using "OPTION list-to-output=1". * The --output option of gpgsm has now an effect on list-keys. * New gpgsm commands --dump-chain and list-chain. * gpg-connect-agent has new options to utilize descriptor passing. * A global trustlist may now be used. See doc/examples/trustlist.txt. * When creating a new pubring.kbx keybox common certificates are imported. Noteworthy changes in version 1.9.22 (2006-07-27) ------------------------------------------------- * Enhanced pkcs#12 support to allow import from simple keyBags. * Exporting to pkcs#12 now create bag attributes so that Mozilla is able to import the files. * Fixed uploading of certain keys to the smart card. Noteworthy changes in version 1.9.21 (2006-06-20) ------------------------------------------------- * New command APDU for scdaemon to allow using it for general card access. Might be used through gpg-connect-agent by using the SCD prefix command. * Support for the CardMan 4040 PCMCIA reader (Linux 2.6.15 required). * Scdaemon does not anymore reset cards at the end of a connection. * Kludge to allow use of Bundesnetzagentur issued X.509 certificates. * Added --hash=xxx option to scdaemon's PKSIGN command. * Pkcs#12 files are now created with a MAC. This is for better interoperability. * Collected bug fixes and minor other changes. Noteworthy changes in version 1.9.20 (2005-12-20) ------------------------------------------------- * Importing pkcs#12 files created be recent versions of Mozilla works again. * Basic support for qualified signatures. * New debug tool gpgparsemail. Noteworthy changes in version 1.9.19 (2005-09-12) ------------------------------------------------- * The Belgian eID card is now supported for signatures and ssh. Other pkcs#15 cards should work as well. * Fixed bug in --export-secret-key-p12 so that certificates are again included. Noteworthy changes in version 1.9.18 (2005-08-01) ------------------------------------------------- * [gpgsm] Now allows for more than one email address as well as URIs and dnsNames in certificate request generation. A keygrip may be given to create a request from an existing key. * A couple of minor bug fixes. Noteworthy changes in version 1.9.17 (2005-06-20) ------------------------------------------------- * gpg-connect-agent has now features to handle Assuan INQUIRE commands. * Internal changes for OpenPGP cards. New Assuan command WRITEKEY. * GNU Pth is now a hard requirement. * [scdaemon] Support for OpenSC has been removed. Instead a new and straightforward pkcs#15 modules has been written. As of now it does allows only signing using TCOS cards but we are going to enhance it to match all the old capabilities. * [gpg-agent] New option --write-env-file and Assuan command UPDATESTARTUPTTY. * [gpg-agent] New option --default-cache-ttl-ssh to set the TTL for SSH passphrase caching independent from the other passphrases. Noteworthy changes in version 1.9.16 (2005-04-21) ------------------------------------------------- * gpg-agent does now support the ssh-agent protocol and thus allows to use the pinentry as well as the OpenPGP smartcard with ssh. * New tool gpg-connect-agent as a general client for the gpg-agent. * New tool symcryptrun as a wrapper for certain encryption tools. * The gpg tool is not anymore build by default because those gpg versions available in the gnupg 1.4 series are far more matured. Noteworthy changes in version 1.9.15 (2005-01-13) ------------------------------------------------- * Fixed passphrase caching bug. * Better support for CCID readers; the reader from Cherry RS 6700 USB does now work. Noteworthy changes in version 1.9.14 (2004-12-22) ------------------------------------------------- * [gpg-agent] New option --use-standard-socket to allow the use of a fixed socket. gpgsm falls back to this socket if GPG_AGENT_INFO has not been set. * Ported to MS Windows with some functional limitations. * New tool gpg-preset-passphrase. Noteworthy changes in version 1.9.13 (2004-12-03) ------------------------------------------------- * [gpgsm] New option --prefer-system-dirmngr. * Minor cleanups and debugging aids. Noteworthy changes in version 1.9.12 (2004-10-22) ------------------------------------------------- * [scdaemon] Partly rewrote the PC/SC code. * Removed the sc-investigate tool. It is now in a separate package available at ftp://ftp.g10code.com/g10code/gscutils/ . * [gpg-agent] Fixed logging problem. Noteworthy changes in version 1.9.11 (2004-10-01) ------------------------------------------------- * When using --import along with --with-validation, the imported certificates are validated and only imported if they are fully valid. * [gpg-agent] New option --max-cache-ttl. * [gpg-agent] When used without --daemon or --server, gpg-agent now check whether a agent is already running and usable. * Fixed some i18n problems. Noteworthy changes in version 1.9.10 (2004-07-22) ------------------------------------------------- * Fixed a serious bug in the checking of trusted root certificates. * New configure option --enable-agent-pnly allows to build and install just the agent. * Fixed a problem with the log file handling. Noteworthy changes in version 1.9.9 (2004-06-08) ------------------------------------------------ * [gpg-agent] The new option --allow-mark-trusted is now required to allow gpg-agent to add a key to the trustlist.txt after user confirmation. * Creating PKCS#10 requests does now honor the key usage. Noteworthy changes in version 1.9.8 (2004-04-29) ------------------------------------------------ * [scdaemon] Overhauled the internal CCID driver. * [scdaemon] Status files named ~/.gnupg/reader_.status are now written when using the internal CCID driver. * [gpgsm] New commands --dump-{,secret,external}-keys to show a very detailed view of the certificates. * The keybox gets now compressed after 3 hours and ephemeral stored certificates are deleted after about a day. * [gpg] Usability fixes for --card-edit. Note, that this has already been ported back to gnupg-1.3 Noteworthy changes in version 1.9.7 (2004-04-06) ------------------------------------------------ * Instrumented the modules for gpgconf. * Added support for DINSIG card applications. * Include the smimeCapabilities attribute with signed messages. * Now uses the gettext domain "gnupg2" to avoid conflicts with gnupg versions < 1.9. Noteworthy changes in version 1.9.6 (2004-03-06) ------------------------------------------------ * Code cleanups and bug fixes. Noteworthy changes in version 1.9.5 (2004-02-21) ------------------------------------------------ * gpg-protect-tool gets now installed into libexec as it ought to be. Cleaned up the build system to better comply with the coding standards. * [gpgsm] The --import command is now able to autodetect pkcs#12 files and import secret and private keys from this file format. A new command --export-secret-key-p12 is provided to allow exporting of secret keys in PKCS\#12 format. * [gpgsm] The pinentry will now present a description of the key for whom the passphrase is requested. * [gpgsm] New option --with-validation to check the validity of key while listing it. * New option --debug-level={none,basic,advanced,expert,guru} to map the debug flags to sensitive levels on a per program base. Noteworthy changes in version 1.9.4 (2004-01-30) ------------------------------------------------ * Added support for the Telesec NKS 2.0 card application. * Added simple tool addgnupghome to create .gnupg directories from /etc/skel/.gnupg. * Various minor bug fixes and cleanups; mainly gpgsm and gpg-agent related. Noteworthy changes in version 1.9.3 (2003-12-23) ------------------------------------------------ * New gpgsm options --{enable,disable}-ocsp to validate keys using OCSP. This option requires a not yet released DirMngr version. Default is disabled. * The --log-file option may now be used to print logs to a socket. Prefix the socket name with "socket://" to enable this. This does not work on all systems and falls back to stderr if there is a problem with the socket. * The options --encrypt-to and --no-encrypt-to now work the same in gpgsm as in gpg. Note, they are also used in server mode. * Duplicated recipients are now silently removed in gpgsm. Noteworthy changes in version 1.9.2 (2003-11-17) ------------------------------------------------ * On card key generation is no longer done using the --gen-key command but from the menu provided by the new --card-edit command. * PINs are now properly cached and there are only 2 PINs visible. The 3rd PIN (CHV2) is internally syncronized with the regular PIN. * All kind of other internal stuff. Noteworthy changes in version 1.9.1 (2003-09-06) ------------------------------------------------ * Support for OpenSC is back. scdaemon supports a --disable-opensc to disable OpenSC use at runtime, so that PC/SC or ct-API can still be used directly. * Rudimentary support for the SCR335 smartcard reader using an internal driver. Requires current libusb from CVS. * Bug fixes. Noteworthy changes in version 1.9.0 (2003-08-05) ------------------------------------------------ ====== PLEASE SEE README-alpha ======= * gpg has been renamed to gpg2 and gpgv to gpgv2. This is a temporary change to allow co-existing with stable gpg versions. * ~/.gnupg/gpg.conf-1.9.0 is fist tried as config file before the usual gpg.conf. * Removed the -k, -kv and -kvv commands. -k is now an alias to --list-keys. New command -K as alias for --list-secret-keys. * Removed --run-as-shm-coprocess feature. * gpg does now also use libgcrypt, libgpg-error is required. * New gpgsm commands --call-dirmngr and --call-protect-tool. * Changing a passphrase is now possible using "gpgsm --passwd" * The content-type attribute is now recognized and created. * The agent does now reread certain options on receiving a HUP. * The pinentry is now forked for each request so that clients with different environments are supported. When running in daemon mode and --keep-display is not used the DISPLAY variable is ignored. * Merged stuff from the newpg branch and started this new development branch. Version 1.4.19 (2015-02-27) Version 1.4.18 (2014-06-30) Version 1.4.17 (2014-06-23) Version 1.4.16 (2013-12-18) Version 1.4.15 (2013-10-04) Version 1.4.14 (2013-07-25) Version 1.4.13 (2012-12-20) Version 1.4.12 (2012-01-30) Version 1.4.11 (2010-10-18) Version 1.4.10 (2009-09-02) Version 1.4.9 (2008-03-26) Version 1.4.8 (2007-12-20) Version 1.4.7 (2007-03-05) Version 1.4.6 (2006-12-06) Version 1.4.5 (2006-08-01) Version 1.4.4 (2006-06-25) Version 1.4.3 (2006-04-03) Version 1.4.2 (2005-07-26) Version 1.4.1 (2005-03-15) Version 1.4.0 (2004-12-16) Noteworthy changes in version 1.3.2 (2003-05-27) ------------------------------------------------ * New "--gnupg" option (set by default) that disables --openpgp, and the various --pgpX emulation options. This replaces --no-openpgp, and --no-pgpX, and also means that GnuPG has finally grown a --gnupg option to make GnuPG act like GnuPG. * A bug in key validation has been fixed. This bug only affects keys with more than one user ID (photo IDs do not count here), and results in all user IDs on a given key being treated with the validity of the most-valid user ID on that key. * Notation names that do not contain a '@' are no longer allowed unless --expert is set. This is to help prevent pollution of the (as yet unused) IETF notation namespace. * Multiple trust models are now supported via the --trust-model option. The options are "pgp" (web-of-trust plus trust signatures), "classic" (web-of-trust only), and "always" (identical to the --always-trust option). * The --personal-{cipher|digest|compression}-preferences are now consulted to get default algorithms before resorting to the last-ditch defaults of --s2k-cipher-algo, SHA1, and ZIP respectively. This allows a user to set algorithms to use in a safe manner so they are used when legal to do so, without forcing them on for all messages. * New --primary-keyring option to designate the keyring that the user wants new keys imported into. * --s2k-digest-algo is now used for all password mangling. Earlier versions used both --s2k-digest-algo and --digest-algo for passphrase mangling. * Handling of --hidden-recipient or --throw-keyid messages is now easier - the user only needs to give their passphrase once, and GnuPG will try it against all of the available secret keys. * Care is taken to prevent compiler optimization from removing memory wiping code. * New option --no-mangle-dos-filenames so that filenames are not truncated in the W32 version. * A "convert-from-106" script has been added. This is a simple script that automates the conversion from a 1.0.6 or earlier version of GnuPG to a 1.0.7 or later version. * Disabled keys are now skipped when selecting keys for encryption. If you are using the --with-colons key listings to detect disabled keys, please see doc/DETAILS for a minor format change in this release. * Minor trustdb changes to make the trust calculations match common usage. * New command "revuid" in the --edit-key menu to revoke a user ID. This is a simpler interface to the old method (which still works) of revoking the user ID self-signature. * Status VALIDSIG does now also print the primary key's fingerprint, as well as the signature version, pubkey algorithm, hash algorithm, and signature class. * Add read-only support for the SHA-256 hash, and optional read-only support for the SHA-384 and SHA-512 hashes. * New option --enable-progress-filter for use with frontends. * DNS SRV records are used in HKP keyserver lookups to allow administrators to load balance and select keyserver ports automatically. This is as specified in draft-shaw-openpgp-hkp-00.txt. * When using the "keyid!" syntax during a key export, only that specified key is exported. If the key in question is a subkey, the primary key plus only that subkey is exported. * configure --disable-xxx options to disable individual algorithms at build time. This can be used to build a smaller gpg binary for embedded uses where space is tight. See the README file for the algorithms that can be used with this option, or use --enable-minimal to build the smallest gpg possible (disables all optional algorithms, disables keyserver access, and disables photo IDs). * The keyserver no-modify flag on a key can now be displayed and modified. * Note that the TIGER/192 digest algorithm is in the process of being dropped from the OpenPGP standard. While this release of GnuPG still contains it, it is disabled by default. To ensure you will still be able to use your messages with future versions of GnuPG and other OpenPGP programs, please do not use this algorithm. See-also: gnupg-announce/2003q2/000153.html Noteworthy changes in version 1.3.1 (2002-11-12) ------------------------------------------------ * Trust signature support. This is based on the Maurer trust model where a user can specify the trust level along with the signature with multiple levels so users can delegate certification ability to other users, possibly restricted by a regular expression on the user ID. Note that full trust signature support requires a regular expression parsing library. The regexp code from glibc 2.3.1 is included for those platforms that don't have working regexp functions available. The configure option --disable-regex may be used to disable any regular expression code, which will make GnuPG ignore any trust signature with a regular expression included. * Two new commands --hidden-recipient (-R) and --hidden-encrypt-to encrypt to a user, but hide the identity of that user. This is the same functionality as --throw-keyid, but can be used on a per-user basis. * Full algorithm names (e.g. "3DES", "SHA1", "ZIP") can now be used interchangeably with the short algorithm names (e.g. "S2", "H2", "Z1") anywhere algorithm names are used in GnuPG. Noteworthy changes in version 1.3.0 (2002-10-18) ------------------------------------------------ * The last piece of internal keyserver support has been removed, and now all keyserver access is done via the keyserver plugins. There is also a newer keyserver protocol used between GnuPG and the plugins, so plugins from earlier versions of GnuPG may not work properly. * The HKP keyserver plugin supports the new machine-readable key listing format for those keyservers that provide it. * When using a HKP keyserver with multiple DNS records (such as wwwkeys.pgp.net which has the addresses of multiple servers around the world), try all records until one succeeds. Note that it depends on the LDAP library used whether the LDAP keyserver plugin does this as well. * The library dependencies for OpenLDAP seem to change fairly frequently, and GnuPG's configure script cannot guess all the combinations. Use ./configure LDAPLIBS="-L libdir -l libs" to override the script and use the libraries selected. * Secret keys generated with --export-secret-subkeys are now indicated in key listings with a '#' after the "sec", and in --with-colons listings by showing no capabilities (no lowercase characters). * --trusted-key has been un-obsoleted, as it is useful for adding ultimately trusted keys from the config file. It is identical to using --edit and "trust" to change a key to ultimately trusted. * Translations other than de are no longer distributed with the development branch. This is due to the frequent text changes during development, which cause the translations to rapidly go out of date. Version 1.2.8 (2006-12-07) Version 1.2.7 (2004-12-27) Version 1.2.6 (2004-08-25) Version 1.2.5 (2004-07-26) Version 1.2.4 (2003-12-23) Version 1.2.3 (2003-08-21) Version 1.2.2 (2003-05-01) Version 1.2.1 (2002-10-25) Version 1.2.0 (2002-09-21) Noteworthy changes in version 1.1.92 (2002-09-11) ------------------------------------------------- * [IMPORTANT] The default configuration file is now ~/.gnupg/gpg.conf. If an old ~/.gnupg/options is found it will still be used. This change is required to have a more consistent naming scheme with forthcoming tools. * The use of MDCs have increased. A MDC will be used if the recipients directly request it, if the recipients have AES, AES192, AES256, or TWOFISH in their cipher preferences, or if the chosen cipher has a blocksize not equal to 64 bits (currently this is also AES, AES192, AES256, and TWOFISH). * GnuPG will no longer automatically disable compression when processing an already-compressed file unless a MDC is being used. This is to give the message a certain amount of resistance to the chosen-ciphertext attack while communicating with other programs (most commonly PGP earlier than version 7.x) that do not support MDCs. * The option --interactive now has the desired effect when importing keys. * The file permission and ownership checks on files have been clarified. Specifically, the homedir (usually ~/.gnupg) is checked to protect everything within it. If the user specifies keyrings outside this homedir, they are presumed to be shared keyrings and therefore *not* checked. Configuration files specified with the --options option and the IDEA cipher extension specified with --load-extension are checked, along with their enclosing directories. * The configure option --with-static-rnd=auto allows to build gpg with all available entropy gathering modules included. At runtime the best usable one will be selected from the list linux, egd, unix. This is also the default for systems lacking a /dev/random device. * The default character set is now taken from the current locale; it can still be overridden by the --charset option. Using the option -vvv shows the used character set. * [REMOVED] --emulate-checksum-bug and --emulate-3des-s2k-bug have been removed. Noteworthy changes in version 1.1.91 (2002-08-04) ------------------------------------------------- * All modules are now linked statically; the --load-extension option is in general not useful anymore. The only exception is to specify the deprecated idea cipher. * The IDEA plugin has changed. Previous versions of the IDEA plugin will no longer work with GnuPG. However, the current version of the plugin will work with earlier GnuPG versions. * When using --batch with one of the --delete-key commands, the key must be specified by fingerprint. See the man page for details. * There are now various ways to restrict the ability GnuPG has to exec external programs (for the keyserver helpers or photo ID viewers). Read the README file for the complete list. * New export option to leave off attribute packets (photo IDs) during export. This is useful when exporting to HKP keyservers which do not understand attribute packets. * New import option to repair during import the HKP keyserver mangling multiple subkeys bug. Note that this cannot completely repair the damaged key as some crucial data is removed by the keyserver, but it does at least give you back one subkey. This is on by default for keyserver --recv-keys, and off by default for regular --import. * The keyserver helper programs now live in /usr/[local/]libexec/gnupg by default. If you are upgrading from 1.0.7, you might want to delete your old copies in /usr/[local/]bin. If you use an OS that does not use libexec for whatever reason, use configure --libexecdir=/usr/local/lib to place the keyserver helpers there. * The LDAP keyserver handler now works properly with very old (version 1) LDAP keyservers. Noteworthy changes in version 1.1.90 (2002-07-01) ------------------------------------------------- * New commands: --personal-cipher-preferences, --personal-digest-preferences, and --personal-compress-preferences allow the user to specify which algorithms are to be preferred. Note that this does not permit using an algorithm that is not present in the recipient's preferences (which would violate the OpenPGP standard). This just allows sorting the preferences differently. * New "group" command to refer to several keys with one name. * A warning is issued if the user forces the use of an algorithm that is not listed in the recipient's preferences. * Full revocation key (aka "designated revoker") support. * The preferred hash algorithms on a key are consulted when encrypting a signed message to that key. Note that this is disabled by default by a SHA1 preference in --personal-digest-preferences. * --cert-digest-algo allows the user to specify the hash algorithm to use when signing a key rather than the default SHA1 (or MD5 for PGP2 keys). Do not use this feature unless you fully understand the implications of this. * --pgp7 mode automatically sets all necessary options to ensure that the resulting message will be usable by a user of PGP 7.x. * New --attribute-fd command for frontends and scripts to get the contents of attribute packets (i.e. photos) * In expert mode, the user can now re-sign a v3 key with a v4 self-signature. This does not change the v3 key into a v4 key, but it does allow the user to use preferences, primary ID flags, etc. * Significantly improved photo ID support on non-unixlike platforms. * The version number has jumped ahead to 1.1.90 to skip over the old version 1.1 and to get ready for the upcoming 1.2. * ElGamal sign and encrypt is not anymore allowed in the key generation dialog unless in expert mode. RSA sign and encrypt has been added with the same restrictions. * [W32] Keyserver access does work with Windows NT. Noteworthy changes in version 1.0.7 (2002-04-29) ------------------------------------------------ * Secret keys are now stored and exported in a new format which uses SHA-1 for integrity checks. This format renders the Rosa/Klima attack useless. Other OpenPGP implementations might not yet support this, so the option --simple-sk-checksum creates the old vulnerable format. * The default cipher algorithm for encryption is now CAST5, default hash algorithm is SHA-1. This will give us better interoperability with other OpenPGP implementations. * Symmetric encrypted messages now use a fixed file size if possible. This is a tradeoff: it breaks PGP 5, but fixes PGP 2, 6, and 7. Note this was only an issue with RFC-1991 style symmetric messages. * Photographic user ID support. This uses an external program to view the images. * Enhanced keyserver support via keyserver "plugins". GnuPG comes with plugins for the NAI LDAP keyserver as well as the HKP email keyserver. It retains internal support for the HKP HTTP keyserver. * Nonrevocable signatures are now supported. If a user signs a key nonrevocably, this signature cannot be taken back so be careful! * Multiple signature classes are usable when signing a key to specify how carefully the key information (fingerprint, photo ID, etc) was checked. * --pgp2 mode automatically sets all necessary options to ensure that the resulting message will be usable by a user of PGP 2.x. * --pgp6 mode automatically sets all necessary options to ensure that the resulting message will be usable by a user of PGP 6.x. * Signatures may now be given an expiration date. When signing a key with an expiration date, the user is prompted whether they want their signature to expire at the same time. * Revocation keys (designated revokers) are now supported if present. There is currently no way to designate new keys as designated revokers. * Permissions on the .gnupg directory and its files are checked for safety. * --expert mode enables certain silly things such as signing a revoked user id, expired key, or revoked key. * Some fixes to build cleanly under Cygwin32. * New tool gpgsplit to split OpenPGP data formats into packets. * New option --preserve-permissions. * Subkeys created in the future are not used for encryption or signing unless the new option --ignore-valid-from is used. * Revoked user-IDs are not listed unless signatures are listed too or we are in verbose mode. * There is no default comment string with ascii armors anymore except for revocation certificates and --enarmor mode. * The command "primary" in the edit menu can be used to change the primary UID, "setpref" and "updpref" can be used to change the preferences. * Fixed the preference handling; since 1.0.5 they were erroneously matched against against the latest user ID and not the given one. * RSA key generation. * Merged Stefan's patches for RISC OS in. See comments in scripts/build-riscos. * It is now possible to sign and conventional encrypt a message (-cs). * The MDC feature flag is supported and can be set by using the "updpref" edit command. * The status messages GOODSIG and BADSIG are now returning the primary UID, encoded using %XX escaping (but with spaces left as spaces, so that it should not break too much) * Support for GDBM based keyrings has been removed. * The entire keyring management has been revamped. * The way signature stati are store has changed so that v3 signatures can be supported. To increase the speed of many operations for existing keyrings you can use the new --rebuild-keydb-caches command. * The entire key validation process (trustdb) has been revamped. See the man page entries for --update-trustdb, --check-trustdb and --no-auto-check-trustdb. * --trusted-keys is again obsolete, --edit can be used to set the ownertrust of any key to ultimately trusted. * A subkey is never used to sign keys. * Read only keyrings are now handled as expected. See-also: gnupg-announce/2002q2/000135.html Noteworthy changes in version 1.0.6 (2001-05-29) ------------------------------------------------ * Security fix for a format string bug in the tty code. * Fixed format string bugs in all PO files. * Removed Russian translation due to too many bugs. The FTP server has an unofficial but better translation in the contrib directory. * Fixed expire time calculation and keyserver access. * The usual set of minor bug fixes and enhancements. * non-writable keyrings are now correctly handled. See-also: gnupg-announce/2001q2/000123.html Noteworthy changes in version 1.0.5 (2001-04-29) ------------------------------------------------ * WARNING: The semantics of --verify have changed to address a problem with detached signature detection. --verify now ignores signed material given on stdin unless this is requested by using a "-" as the name for the file with the signed material. Please check all your detached signature handling applications and make sure that they don't pipe the signed material to stdin without using a filename together with "-" on the the command line. * WARNING: Corrected hash calculation for input data larger than 512M - it was just wrong, so you might notice bad signature in some very big files. It may be wise to keep an old copy of GnuPG around. * Secret keys are no longer imported unless you use the new option --allow-secret-key-import. This is a kludge and future versions will handle it in another way. * New command "showpref" in the --edit-key menu to show an easier to understand preference listing. * There is now the notation of a primary user ID. For example, it is printed with a signature verification as the first user ID; revoked user IDs are not printed there anymore. In general the primary user ID is the one with the latest self-signature. * New --charset=utf-8 to bypass all internal conversions. * Large File Support (LFS) is now working. * New options: --ignore-crc-error, --no-sig-create-check, --no-sig-cache, --fixed-list-mode, --no-expensive-trust-checks, --enable-special-filenames and --use-agent. See man page. * New command --pipemode, which can be used to run gpg as a co-process. Currently only the verification of detached signatures are working. See doc/DETAILS. * Keyserver support for the W32 version. * Rewritten key selection code so that GnuPG can better cope with multiple subkeys, expire dates and so. The drawback is that it is slower. * A whole lot of bug fixes. * The verification status of self-signatures are now cached. To increase the speed of key list operations for existing keys you can do the following in your GnuPG homedir (~/.gnupg): cp pubring.gpg pubring.gpg.save && gpg --export-all >x && \ rm pubring.gpg && gpg --import x Only v4 keys (i.e not the old RSA keys) benefit from this caching. * New translations: Estonian, Turkish. See-also: gnupg-announce/2001q2/000122.html Noteworthy changes in version 1.0.4 (2000-10-17) ------------------------------------------------ * Fixed a serious bug which could lead to false signature verification results when more than one signature is fed to gpg. This is the primary reason for releasing this version. * New utility gpgv which is a stripped down version of gpg to be used to verify signatures against a list of trusted keys. * Rijndael (AES) is now supported and listed with top preference. * --with-colons now works with --print-md[s]. See-also: gnupg-announce/2000q4/000082.html Noteworthy changes in version 1.0.3 (2000-09-18) ------------------------------------------------ * Fixed problems with piping to/from other MS-Windows software * Expiration time of the primary key can be changed again. * Revoked user IDs are now marked in the output of --list-key * New options --show-session-key and --override-session-key to help the British folks to somewhat minimize the danger of this Orwellian RIP bill. * New options --merge-only and --try-all-secrets. * New configuration option --with-egd-socket. * The --trusted-key option is back after it left us with 0.9.5 * RSA is supported. Key generation does not yet work but will come soon. * CAST5 and SHA-1 are now the default algorithms to protect the key and for symmetric-only encryption. This should solve a couple of compatibility problems because the old algorithms are optional according to RFC2440 * Twofish and MDC enhanced encryption is now used. PGP 7 supports this. Older versions of GnuPG don't support it, so they should be upgraded to at least 1.0.2 See-also: gnupg-announce/2000q3/000075.html Noteworthy changes in version 1.0.2 (2000-07-12) ---------------------------------------------- * Fixed expiration handling of encryption keys. * Add an experimental feature to do unattended key generation. * The user is now asked for the reason of revocation as required by the new OpenPGP draft. * There is a ~/.gnupg/random_seed file now which saves the state of the internal RNG and increases system performance somewhat. This way the full entropy source is only used in cases were it is really required. Use the option --no-random-seed-file to disable this feature. * New options --ignore-time-conflict and --lock-never. * Some fixes for the W32 version. * The entropy.dll is not anymore used by the W32 version but replaced by code derived from Cryptlib. * Encryption is now much faster: About 2 times for 1k bit keys and 8 times for 4k keys. * New encryption keys are generated in a way which allows a much faster decryption. * New command --export-secret-subkeys which outputs the the _primary_ key with it's secret parts deleted. This is useful for automated decryption/signature creation as it allows to keep the real secret primary key offline and thereby protecting the key certificates and allowing to create revocations for the subkeys. See the FAQ for a procedure to install such secret keys. * Keygeneration now writes to the first writeable keyring or as default to the one in the homedirectory. Prior versions ignored all --keyring options. * New option --command-fd to take user input from a file descriptor; to be used with --status-fd by software which uses GnuPG as a backend. * There is a new status PROGRESS which is used to show progress during key generation. * Support for the new MDC encryption packets. To create them either --force-mdc must be use or cipher algorithm with a blocksize other than 64 bits is to be used. --openpgp currently disables MDC packets entirely. This option should not yet be used. * New option --no-auto-key-retrieve to disable retrieving of a missing public key from a keyserver, when a keyserver has been set. * Danish translation See-also: gnupg-announce/2000q3/000069.html Noteworthy changes in version 1.0.1 (1999-12-16) ----------------------------------- * New command --verify-files. New option --fast-list-mode. * $http_proxy is now used when --honor-http-proxy is set. * Fixed some minor bugs and the problem with conventional encrypted packets which did use the gpg v3 partial length headers. * Add Indonesian and Portugese translations. * Fixed a bug with symmetric-only encryption using the non-default 3DES. The option --emulate-3des-s2k-bug may be used to decrypt documents which have been encrypted this way; this should be done immediately as this workaround will be remove in 1.1 * Can now handle (but not display) PGP's photo IDs. I don't know the format of that packet but after stripping a few bytes from the start it looks like a JPEG (at least my test data). Handling of this package is required because otherwise it would mix up the self signatures and you can't import those keys. * Passing non-ascii user IDs on the commandline should now work in all cases. * New keys are now generated with an additional preference to Blowfish. * Removed the GNU Privacy Handbook from the distribution as it will go into a separate one. See-also: gnupg-announce/1999q4/000050.html Noteworthy changes in version 1.0.0 (1999-09-07) ----------------------------------- * Add a very preliminary version of the GNU Privacy Handbook to the distribution (lynx doc/gph/index.html). * Changed the version number to GnuPG 2001 ;-) See-also: gnupg-announce/1999q3/000037.html Noteworthy changes in version 0.9.11 (1999-09-03) ------------------------------------ * UTF-8 strings are now correctly printed (if --charset is set correctly). Output of --with-colons remains C-style escaped UTF-8. * Workaround for a problem with PGP 5 detached signature in textmode. * Fixed a problem when importing new subkeys (duplicated signatures). See-also: gnupg-announce/1999q3/000036.html Noteworthy changes in version 0.9.10 (1999-07-23) ------------------------------------ * Some strange new options to help pgpgpg * Cleaned up the dox a bit. See-also: gnupg-announce/1999q3/000034.html Noteworthy changes in version 0.9.9 ----------------------------------- * New options --[no-]utf8-strings. * New edit-menu commands "enable" and "disable" for entire keys. * You will be asked for a filename if gpg cannot deduce one. * Changes to support libtool which is needed for the development of libgcrypt. * New script tools/lspgpot to help transferring assigned trustvalues from PGP to GnuPG. * New commands --lsign-key and made --sign-key a shortcut for --edit and sign. * New options (#122--126 ;-) --[no-]default-recipient[-self], --disable-{cipher,pubkey}-algo. See the man page. * Enhanced info output in case of multiple recipients and fixed exit code. * New option --allow-non-selfsigned-uid to work around a problem with the German IN way of separating signing and encryption keys. See-also: gnupg-announce/1999q3/000028.html Noteworthy changes in version 0.9.8 (1999-06-26) ----------------------------------- * New subcommand "delsig" in the edit menu. * The name of the output file is not anymore the one which is embedded in the processed message, but the used filename with the extension stripped. To revert to the old behaviour you can use the option --use-embedded-filename. * Another hack to cope with pgp2 generated detached signatures. * latin-2 character set works (--charset=iso-8859-2). * New option --with-key-data to list the public key parameters. New option -N to insert notations and a --set-policy-url. A couple of other options to allow reseting of options. * Better support for HPUX. See-also: gnupg-announce/1999q2/000016.html Noteworthy changes in version 0.9.7 (1999-05-23) ----------------------------------- * Add some work arounds for a bugs in pgp 2 which led to bad signatures when used with canonical texts in some cases. * Enhanced some status outputs. See-also: gnupg-announce/1999q2/000000.html Noteworthy changes in version 0.9.6 (1999-05-06) ----------------------------------- * Twofish is now statically linked by default. The experimental 128 bit version is now disabled. Full support will be available as soon as the OpenPGP WG has decided on an interpretation of rfc2440. * Dropped support for the ancient Blowfish160 which is not OpenPGP. * Merged gpgm and gpg into one binary. * Add "revsig" and "revkey" commands to the edit menu. It is now possible to revoke signature and subkeys. Noteworthy changes in version 0.9.5 (1999-03-20) ----------------------------------- * New command "lsign" in the keyedit menu to create non-exportable signatures. Removed --trusted-keys option. * A bunch of changes to the key validation code. * --list-trust-path now has an optional --with-colons format. * New command --recv-keys to import keys from an keyserver. Noteworthy changes in version 0.9.4 (1999-03-08) ----------------------------------- * New configure option --enable-static-rnd=[egd|linux|unix|none] to select a random gathering module for static linking. * The original text is now verbatim copied to a cleartext signed message. * Bugfixes but there are still a couple of bugs. Noteworthy changes in version 0.9.3 (1999-02-19) ----------------------------------- * Changed the internal design of getkey which now allows a efficient lookup of multiple keys and add a word match mode. * New options --[no-]encrypt-to. * Some changes to the configure stuff. Switched to automake 1.4. Removed intl/ from CVS, autogen.sh now uses gettextize. * Preferences now include Twofish. Removed preference to Blowfish with a special hack to suppress the "not listed in preferences" warning; this is to allow us to switch completely to Twofish in the near future. * Changed the locking stuff. * Print all user ids of a good signature. Noteworthy changes in version 0.9.2 (1999-01-01) ----------------------------------- * add some additional time warp checks. * Option --keyserver and command --send-keys to utilize HKP servers. * Upgraded to zlib 1.1.3 and fixed an inflate bug * More cleanup on the cleartext signatures. Noteworthy changes in version 0.9.1 (1999-01-01) ----------------------------------- * Polish language support. * When querying the passphrase, the key ID of the primary key is displayed along with the one of the used secondary key. * Fixed a bug occurring when decrypting pgp 5 encrypted messages, fixed an infinite loop bug in the 3DES code and in the code which looks for trusted signatures. * Fixed a bug in the mpi library which caused signatures not to compare okay. * Rewrote the handling of cleartext signatures; the code is now better maintainable (I hope so). * New status output VALIDSIG only for valid signatures together with the fingerprint of the signer's key. Noteworthy changes in version 0.9.0 (1998-12-23) ----------------------------------- * --export does now only exports rfc2440 compatible keys; the old behaviour is available with --export-all. Generation of v3 ElGamal (sign and encrypt) keys is not longer supported. * Fixed the uncompress bug. * Rewrote the rndunix module. There are two environment variables used for debugging now: GNUPG_RNDUNIX_DBG give the file to write debugging information (use "-" for stdout) and if GNUPG_RNDUNIX_DBGALL is set, all programs which are only tried are also printed. * New option --escape-from-lines to "dash-escape" "From " lines to prevent mailers to change them to ">From ". This is not enabled by default because it is not in compliance with rfc2440 - however, you should turn it on. Noteworthy changes in version 0.4.5 (1998-12-08) ----------------------------------- * The keyrings and the trustdb is now locked, so that other GnuPG processes won't damage these files. You may want to put the option --lock-once into your options file. * The latest self-signatures are now used; this enables --import to see updated preferences etc. * Import of subkeys should now work. * Random gathering modules may now be loaded as extensions. Add such a module for most Unices but it is very experimental! * Brazilian language support. Noteworthy changes in version 0.4.4 (1998-11-20) ----------------------------------- * Fixed the way the key expiration time is stored. If you have an expiration time on your key you should fix it with --edit-key and the command "expire". I apologize for this inconvenience. * Add option --charset to support "koi8-r" encoding of user ids. (Not yet tested). * Preferences should now work again. You should run "gpgm --check-trustdb \*" to rebuild all preferences. * Checking of certificates should now work but this needs a lot of testing. Key validation values are now cached in the trustdb; they should be recalculated as needed, but you may use --check-trustdb or --update-trustdb to do this. * Spanish translation by Urko Lusa. * Patch files are from now on signed. See the man page for the new option --not-dash-escaped. * New syntax: --edit-key [] If you run it without --batch the commands are executed and then you are put into normal mode unless you use "quit" or "save" as one of the commands. When in batch mode, the program quits after the last command, so you have to use "save" if you did some changes. It does not yet work completely, but may be used to list so the keys etc. Noteworthy changes in version 0.4.3 (1998-11-08) ----------------------------------- * Fixed the gettext configure bug. * Kludge for RSA keys: keyid and length of a RSA key are correctly reported, but you get an error if you try to use this key (If you do not have the non-US version). * Experimental support for keyrings stored in a GDBM database. This is *much* faster than a standard keyring. You will notice that the import gets slower with time; the reason is that all new keys are used to verify signatures of previous inserted keys. Use "--keyring gnupg-gdbm:". This is not (yet) supported for secret keys. * A Russian language file in the distribution (alternatives are in the contrib directory of the FTP servers) * commandline option processing now works as expected for GNU programs with the exception that you can't mix options and normal arguments. * Now --list-key lists all matching keys. This is needed in some other places too. Noteworthy changes in version 0.4.2 (1998-10-18) ----------------------------------- * This is only a snapshot: There are still a few bugs. * Fixed this huge memory leak. * Redesigned the trust database: You should run "gpgm --check-trustdb". New command --update-trustdb, which adds new key from the public keyring into your trustdb * Fixed a bug in the armor code, leading to invalid packet errors. (a workaround for this was to use --no-armor). The shorten line length (64 instead of 72) fixes a problem with pgp5 and keyservers. * comment packets are not anymore generated. "--export" filters them out. One Exception: The comment packets in a secret keyring are still used because they carry the factorization of the public prime product. * --import now only looks for KEYBLOCK headers, so you can now simply remove the "- " in front of such a header if someone accidentally signed such a message or the keyblock is part of a cleartext signed message. * --with-colons now lists the key expiration time and not anymore the valid period. * Some keyblocks created with old releases have a wrong sequence of packets, so that the keyservers don't accept these keys. Simply using "--edit-key" fixes the problem. * New option --force-v3-sigs to generate signed messages which are compatible to PGP 5. * Add some code to support DLD (for non ELF systems) - but this is not tested because my BSD box is currently broken. * New command "expire" in the edit-key menu. Noteworthy changes in version 0.4.1 (1998-10-07) ----------------------------------- * A secondary key is used when the primary key is specified but cannot be used for the operation (if it is a sign-only key). * GNUPG can now handle concatenated armored messages: There is still a bug if different kinds of messages are mixed. * Iterated+Salted passphrases now work. If want to be sure that PGP5 is able to handle them you may want to use the options "--s2k-mode 3 --s2k-cipher-algo cast5 --s2k-digest-algo sha1" when changing a passphrase. * doc/OpenPGP talks about OpenPGP compliance, doc/HACKING gives a few hints about the internal structure. * Checked gnupg against the August 1998 draft (07) and I believe it is in compliance with this document (except for one point). * Fixed some bugs in the import merging code and rewrote some code for the trustdb. Noteworthy changes in version 0.4.0 (1998-09-18) ----------------------------------- * Triple DES is now supported. Michael Roth did this piece of needed work. We have now all the coded needed to be OpenPGP compliant. * Added a simple rpm spec file (see INSTALL). * detached and armored signatures are now using "PGP SIGNATURE", except when --rfc1991 is used. * All times which are not in the yyyy-mm-dd format are now printed in local time. Noteworthy changes in version 0.3.5 (1998-09-14) ----------------------------------- * New option --throw-keyid to create anonymous enciphered messages. If gpg detects such a message it tires all available secret keys in turn so decode it. This is a gnupg extension and not in OpenPGP but it has been discussed there and afaik some products use this scheme too (Suggested by Nimrod Zimmerman). * Fixed a bug with 5 byte length headers. * --delete-[secret-]key is now also available in gpgm. * cleartext signatures are not anymore converted to LF only. * Fixed a trustdb problem. Run "gpgm --check-trustdb" to fix old trust dbs. * Building in another directory should now work. * Weak key detection mechanism (Niklas Hernaeus). Noteworthy changes in version 0.3.4 (1998-08-11) ----------------------------------- * New options --comment and --set-filename; see g10/OPTIONS * yes/no, y/n localized. * Fixed some bugs. Noteworthy changes in version 0.3.3 (1998-08-08) ----------------------------------- * IMPORTANT: I found yet another bug in the way the secret keys are encrypted - I did it the way pgp 2.x did it, but OpenPGP and pgp 5.x specify another (in some aspects simpler) method. To convert your secret keys you have to do this: 1. Build the new release but don't install it and keep a copy of the old program. 2. Disable the network, make sure that you are the only user, be sure that there are no Trojan horses etc .... 3. Use your old gpg (version 0.3.1 or 0.3.2) and set the passphrases of ALL your secret keys to empty! (gpg --change-passphrase your-user-id). 4. Save your ownertrusts (see the next point) 5. rm ~/.gnupg/trustdb.gpg 6. install the new version of gpg (0.3.3) 7. For every secret key call "gpg --edit-key your-user-id", enter "passwd" at the prompt, follow the instructions and change your password back, enter "save" to store it. 8. Restore the ownertrust (see next point). * The format of the trust database has changed; you must delete the old one, so gnupg can create a new one. IMPORTANT: Use version 0.3.1 or .2 to save your assigned ownertrusts ("gpgm --list-ownertrust >saved-trust"); then build this new version and restore the ownertrust with this new version ("gpgm --import-ownertrust saved-trust"). Please note that --list-ownertrust has been renamed to --export-ownertrust in this release and it does now only export defined ownertrusts. * The command --edit-key now provides a commandline driven menu which can be used for various tasks. --sign-key is only an an alias to --edit-key and maybe removed in future: use the command "sign" of this new menu - you can select which user ids you want to sign. * Alternate user ids can now be created an signed. * Owner trust values can now be changed with --edit-key (trust) * GNUPG can now run as a coprocess; this enables sophisticated frontends. tools/shmtest.c is a simple sample implementation. This needs some more work: all tty_xxx() are to be replaced by cpr_xxx() and some changes in the display logics is needed. * Removed options --gen-prime and --gen-random. * Removed option --add-key; use --edit-key instead. * Removed option --change-passphrase; use --edit-key instead. * Signatures are now checked even if the output file could not be created. Command "--verify" tries to find the detached data. * gpg now disables core dumps. * compress and symmetric cipher preferences are now used. Because there is no 3DES yet, this is replaced by Blowfish. * We have added the Twofish as an experimental cipher algorithm. Many thanks to Matthew Skala for doing this work. Twofish is the AES submission from Schneier et al.; see "www.counterpane.com/twofish.html" for more information. * Started with a help system: If you enter a question mark at some prompt; you should get a specific help for this prompt. * There is no more backup copy of the secret keyring. * A lot of new bugs. I think this release is not as stable as the previous one. Noteworthy changes in version 0.3.2 (1998-07-09) ----------------------------------- * Fixed some bugs when using --textmode (-seat) * Now displays the trust status of a positive verified message. * Keyrings are now scanned in the sequence they are added with --[secret-]keyring. Note that the default keyring is implicitly added as the very first one unless --no-default-keyring is used. * Fixed setuid and dlopen bug. Noteworthy changes in version 0.3.1 (1998-07-06) ----------------------------------- * Partial headers are now written in the OpenPGP format if a key in a v4 packet is used. * Removed some unused options, removed the gnupg.sig stuff. * Key lookup by name now returns a key which can be used for the desired action. * New options --list-ownertrust (gpgm) to make a backup copy of the ownertrust values you assigned. * clear signature headers are now in compliance with OpenPGP. Noteworthy changes in version 0.3.0 (1998-06-25) ----------------------------------- * New option --emulate-checksum-bug. If your passphrase does not work anymore, use this option and --change-passphrase to rewrite your passphrase. * More complete v4 key support: Preferences and expiration time is set into the self signature. * Key generation defaults to DSA/ElGamal keys, so that new keys are interoperable with pgp5 * DSA key generation is faster and key generation does not anymore remove entropy from the random generator (the primes are public parameters, so there is really no need for a cryptographic secure prime number generator which we had used). * A complete new structure for representing the key parameters. * Removed most public key knowledge into the cipher library. * Support for dynamic loading of new algorithms. * Moved tiger to an extension module. Noteworthy changes in version 0.2.19 (1998-05-29) ------------------------------------ * Replaced /dev/urandom in checks with new tool mk-tdata. * Some assembler file cleanups; some more functions for the Alpha. * Tiger has now the OpenPGP assigned number 6. Because the OID has changed, old signatures using this algorithm can't be verified. * gnupg now encrypts the compressed packed and not any longer in the reverse order; anyway it can decrypt both versions. Thanks to Tom for telling me this (not security related) bug. * --add-key works and you are now able to generate subkeys. * It is now possible to generate ElGamal keys in v4 packets to create valid OpenPGP keys. * Some new features for better integration into MUAs. Noteworthy changes in version 0.2.18 (1998-05-15) ------------------------------------ * Splitted cipher/random.c, add new option "--disable-dev-random" to configure to support the development of a random source for other systems. Prepared sourcefiles rand-unix.c, rand-w32.c and rand-dummy.c (which is used to allow compilation on systems without a random source). * Fixed a small bug in the key generation (it was possible that 48 bits of a key were not taken from the random pool) * Add key generation for DSA and v4 signatures. * Add a function trap_unaligned(), so that a SIGBUS is issued on Alphas and not the slow emulation code is used. And success: rmd160 raised a SIGBUS. * Enhanced the formatting facility of argparse and changed the use of \r,\v to @ because gettext does not like it. * New option "--compress-algo 1" to allow the creation of compressed messages which are readable by PGP and "--print-md" (gpgm) to make speed measurement easier. Noteworthy changes in version 0.2.17 (1998-05-04) ------------------------------------ * Comment packets are now of private type 61. * Passphrase code still used a 160 bit blowfish key, added a silly workaround. Please change your passphrase again - sorry. * Conventional encryption now uses a type 3 packet to describe the used algorithms. * The new algorithm number for Blowfish is 20, 16 is still used for encryption only; for signing it is only used when it is in a v3 packet, so that GNUPG keys are still valid. Noteworthy changes in version 0.2.16 (1998-04-28) ------------------------------------ * Add experimental support for the TIGER/192 message digest algorithm. (But there is only a dummy ASN OID). * Standard cipher is now Blowfish with 128 bit key in OpenPGP's CFB mode. I renamed the old cipher to Blowfish160. Because the OpenPGP group refused to assign me a number for Blowfish160, I have to drop support for this in the future. You should use "--change-passphrase" to recode your current passphrase with 128 bit Blowfish. Noteworthy changes in version 0.2.15 (1998-04-09) ------------------------------------ * Fixed a bug with the old checksum calculation for secret keys. If you run the program without --batch, a warning does inform you if your secret key needs to be converted; simply use --change-passphrase to recalculate the checksum. Please do this soon, as the compatible mode will be removed sometime in the future. * CAST5 works (using the PGP's special CFB mode). * Again somewhat more PGP 5 compatible. * Some new test cases Noteworthy changes in version 0.2.14 (1998-04-02) ------------------------------------ * Changed the internal handling of keyrings. * Add support to list PGP 5 keyrings with subkeys * Timestamps of signatures are now verified. * A expiration time can now be specified during key generation. * Some speedups for Blowfish and SHA-1, rewrote SHA-1 transform. Reduced the amount of random bytes needed for key generation in some cases. Noteworthy changes in version 0.2.13 (1998-03-10) ------------------------------------ * Verify of DSA signatures works. * Re-implemented the slower random number generator. Noteworthy changes in version 0.2.12 (1998-03-07) ------------------------------------ * --delete-key checks that there is no secret key. The new option --delete-secret-key maybe used to delete a secret key. * "-kv" now works as expected. Options "--list-{keys,sigs]" and "--check-sigs" are now working. * New options "--verify" and "--decrypt" to better support integration into MUAs (partly done for Mutt). * New option "--with-colons" to make parsing of key lists easier. Noteworthy changes in version 0.2.11 (1998-03-02) ------------------------------------ * GPG now asks for a recipient's name if option "-r" is not used. * If there is no good trust path, the program asks whether to use the public keys anyway. * "--delete-key" works for public keys. What semantics shall I use when there is a secret key too? Delete the secret key or leave him and auto-regenerate the public key, next time the secret key is used? Noteworthy changes in version 0.2.10 (1998-02-27) ------------------------------------ * Code for the alpha is much faster (about 20 times); the data was misaligned and the kernel traps this, so nearly all time was used by system to trap the misalignments and to write syslog messages. Shame on me and thanks to Ralph for pointing me at this while drinking some beer yesterday. * Changed some configure options and add an option --disable-m-guard to remove the memory checking code and to compile everything with optimization on. * New environment variable GNUPGHOME, which can be used to set another homedir than ~/.gnupg. Changed default homedir for Windoze version to c:/gnupg. * Fixed detached signatures; detached PGP signatures caused a SEGV. * The Windoze version works (as usual w/o a strong RNG). Noteworthy changes in version 0.2.9 (1998-02-26) ----------------------------------- * Fixed FreeBSD bug. * Added a simple man page. * Switched to automake1.2f and a newer gettext. Noteworthy changes in version 0.2.8 (1998-02-24) ----------------------------------- * Changed the name to GNUPG, the binaries are called gpg and gpgm. You must rename rename the directory "~/.g10" to ~/.gnupg/, rename {pub,sec}ring.g10 to {pub,sec}ring.gpg, trustdb.g10 to trustdb.gpg and g10.sig to gnupg.sig. * New or changed passphrases are now salted. Noteworthy changes in version 0.2.7 (1998-02-18) ----------------------------------- * New command "gen-revoke" to create a key revocation certificate. * New option "homedir" to set the homedir (which defaults to "~/.g10"). This directory is created if it does not exists (only the last part of the name and not the complete hierarchy) * Command "import" works. (Try: "finger gcrypt@ftp.guug.de|g10 --import") * New commands "dearmor/enarmor" for g10maint. These are mainly used for internal test purposes. * Option --version now conforming to the GNU standards and lists the available ciphers, message digests and public key algorithms. * Assembler code for m68k (not tested). * "make check" works. Noteworthy changes in version 0.2.6 (1998-02-13) ----------------------------------- * Option "--export" works. Noteworthy changes in version 0.2.5 (1998-02-12) ----------------------------------- * Added zlib for systems which don't have it. Use "./configure --with-zlib" to link with the static version. * Generalized some more functions and rewrote the encoding of message digests into MPIs. * Enhanced the checkit script Noteworthy changes in version 0.2.4 (1998-02-11) ----------------------------------- * nearly doubled the speed of the ElGamal signature verification. * backup copies of keyrings are created. * assembler stuff for Pentium; gives about 15% better performance. * fixed a lot of bugs. Noteworthy changes in version 0.2.3 (1998-02-09) ----------------------------------- * Found a bug in the calculation of ELG fingerprints. This is now fixed, but all existing fingerprints and keyids for ELG keys are not any more valid. * armor should now work; including clear signed text. * moved some options to the new program g10maint * It's now 64 bit clean and runs fine on an alpha--linux. * Key generation is much faster now. I fixed this by using not so strong random number for the primes (this was a bug because the ElGamal primes are public parameters and it does not make sense to generate them from strong random). The real secret is the x value which is still generated from strong (okay: /dev/random) random bits. * added option "--status-fd": see g10/OPTIONS * We have secure memory on systems which support mlock(). It is not complete yet, because we do not have signal handler which does a cleanup in very case. We should also check the ulimit for the user in the case that the admin does not have set a limit on locked pages. * started with internationalization support. * The logic to handle the web of trust is now implemented. It is has some bugs; but I'm going to change the algorithm anyway. It works by calculating the trustlevel on the fly. It may ask you to provide trust parameters if the calculated trust probability is too low. I will write a paper which discusses this new approach. * a couple of changes to the configure script. * New option "--quick-random" which uses a much quicker random number generator. Keys generated while this option is in effect are flags with "INSECURE!" in the user-id. This is a development only option. * Read support for new version packets (OpenPGP). * Comment packets are now of correct OpenPGP type 16. Old comment packets written by G10 are detected because they always start with a hash which is an invalid version byte. * The string "(INSECURE!)" is appended to a new user-id if this is generated on a system without a good random number generator. Version 0.2.2 (1998-02-09) Version 0.2.1 (1998-01-28) Version 0.2.0 (1998-01-25) Version 0.1.3 (1998-01-12) Version 0.1.2 (1998-01-07) Version 0.1.1 (1998-01-07) Version 0.1.0 (1998-01-05) Version 0.0.0 (1997-12-20) Copyright (C) 1998-2017 Free Software Foundation, Inc. Copyright (C) 1997-2017 Werner Koch This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without modifications, as long as this notice is preserved. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY, to the extent permitted by law; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/agent/agent.h b/agent/agent.h index 9fdbc76d3..9baf59601 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -1,605 +1,606 @@ /* agent.h - Global definitions for the agent * Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef AGENT_H #define AGENT_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT #include #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include #include #include "../common/util.h" #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/session-env.h" #include "../common/shareddefs.h" /* To convey some special hash algorithms we use algorithm numbers reserved for application use. */ #ifndef GCRY_MODULE_ID_USER #define GCRY_MODULE_ID_USER 1024 #endif #define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1) /* Maximum length of a digest. */ #define MAX_DIGEST_LEN 64 /* The maximum length of a passphrase (in bytes). Note: this is further contrained by the Assuan line length (and any other text on the same line). However, the Assuan line length is 1k bytes so this shouldn't be a problem in practice. */ #define MAX_PASSPHRASE_LEN 255 /* A large struct name "opt" to keep global flags */ struct { unsigned int debug; /* Debug flags (DBG_foo_VALUE) */ int verbose; /* Verbosity level */ int quiet; /* Be as quiet as possible */ int dry_run; /* Don't change any persistent data */ int batch; /* Batch mode */ /* True if we handle sigusr2. */ int sigusr2_enabled; /* Environment settings gathered at program start or changed using the Assuan command UPDATESTARTUPTTY. */ session_env_t startup_env; char *startup_lc_ctype; char *startup_lc_messages; /* Enable pinentry debugging (--debug 1024 should also be used). */ int debug_pinentry; /* Filename of the program to start as pinentry. */ const char *pinentry_program; /* Filename of the program to handle smartcard tasks. */ const char *scdaemon_program; int disable_scdaemon; /* Never use the SCdaemon. */ int no_grab; /* Don't let the pinentry grab the keyboard */ /* The name of the file pinentry shall touch before exiting. If this is not set the file name of the standard socket is used. */ const char *pinentry_touch_file; /* A string where the first character is used by the pinentry as a custom invisible character. */ char *pinentry_invisible_char; /* The timeout value for the Pinentry in seconds. This is passed to the pinentry if it is not 0. It is up to the pinentry to act upon this timeout value. */ unsigned long pinentry_timeout; /* The default and maximum TTL of cache entries. */ unsigned long def_cache_ttl; /* Default. */ unsigned long def_cache_ttl_ssh; /* for SSH. */ unsigned long max_cache_ttl; /* Default. */ unsigned long max_cache_ttl_ssh; /* for SSH. */ /* Flag disallowing bypassing of the warning. */ int enforce_passphrase_constraints; /* The require minmum length of a passphrase. */ unsigned int min_passphrase_len; /* The minimum number of non-alpha characters in a passphrase. */ unsigned int min_passphrase_nonalpha; /* File name with a patternfile or NULL if not enabled. */ const char *check_passphrase_pattern; /* If not 0 the user is asked to change his passphrase after these number of days. */ unsigned int max_passphrase_days; /* If set, a passphrase history will be written and checked at each passphrase change. */ int enable_passphrase_history; /* If set the extended key format is used for new keys. */ int enable_extended_key_format; int running_detached; /* We are running detached from the tty. */ /* If this global option is true, the passphrase cache is ignored for signing operations. */ int ignore_cache_for_signing; /* If this global option is true, the user is allowed to interactively mark certificate in trustlist.txt as trusted. */ int allow_mark_trusted; /* If this global option is true, the Assuan command PRESET_PASSPHRASE is allowed. */ int allow_preset_passphrase; /* If this global option is true, the Assuan option pinentry-mode=loopback is allowed. */ int allow_loopback_pinentry; /* Allow the use of an external password cache. If this option is enabled (which is the default) we send an option to Pinentry to allow it to enable such a cache. */ int allow_external_cache; /* If this global option is true, the Assuan option of Pinentry allow-emacs-prompt is allowed. */ int allow_emacs_pinentry; int keep_tty; /* Don't switch the TTY (for pinentry) on request */ int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */ /* This global option indicates the use of an extra socket. Note that we use a hack for cleanup handling in gpg-agent.c: If the value is less than 2 the name has not yet been malloced. */ int extra_socket; /* This global option indicates the use of an extra socket for web browsers. Note that we use a hack for cleanup handling in gpg-agent.c: If the value is less than 2 the name has not yet been malloced. */ int browser_socket; /* The digest algorithm to use for ssh fingerprints when * communicating with the user. */ int ssh_fingerprint_digest; /* The value of the option --s2k-count. If this option is not given * or 0 an auto-calibrated value is used. */ unsigned long s2k_count; } opt; /* Bit values for the --debug option. */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */ /* Test macros for the debug option. */ #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) /* Forward reference for local definitions in command.c. */ struct server_local_s; /* Declaration of objects from command-ssh.c. */ struct ssh_control_file_s; typedef struct ssh_control_file_s *ssh_control_file_t; /* Forward reference for local definitions in call-scd.c. */ struct scd_local_s; /* Collection of data per session (aka connection). */ struct server_control_s { /* Private data used to fire up the connection thread. We use this structure do avoid an extra allocation for only a few bytes while spawning a new connection thread. */ struct { gnupg_fd_t fd; } thread_startup; /* Flag indicating the connection is run in restricted mode. A value of 1 if used for --extra-socket, a value of 2 is used for --browser-socket. */ int restricted; /* Private data of the server (command.c). */ struct server_local_s *server_local; /* Private data of the SCdaemon (call-scd.c). */ struct scd_local_s *scd_local; /* Environment settings for the connection. */ session_env_t session_env; char *lc_ctype; char *lc_messages; unsigned long client_pid; int client_uid; /* The current pinentry mode. */ pinentry_mode_t pinentry_mode; /* The TTL used for the --preset option of certain commands. */ int cache_ttl_opt_preset; /* Information on the currently used digest (for signing commands). */ struct { int algo; unsigned char value[MAX_DIGEST_LEN]; int valuelen; int raw_value: 1; } digest; unsigned char keygrip[20]; int have_keygrip; /* A flag to enable a hack to send the PKAUTH command instead of the PKSIGN command to the scdaemon. */ int use_auth_call; /* A flag to inhibit enforced passphrase change during an explicit passwd command. */ int in_passwd; /* The current S2K which might be different from the calibrated count. */ unsigned long s2k_count; /* If pinentry is active for this thread. It can be more than 1, when pinentry is called recursively. */ int pinentry_active; }; /* Information pertaining to pinentry requests. */ struct pin_entry_info_s { int min_digits; /* min. number of digits required or 0 for freeform entry */ int max_digits; /* max. number of allowed digits allowed*/ int max_tries; /* max. number of allowed tries. */ int failed_tries; /* Number of tries so far failed. */ int with_qualitybar; /* Set if the quality bar should be displayed. */ int with_repeat; /* Request repetition of the passphrase. */ int repeat_okay; /* Repetition worked. */ gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check the PIN */ void *check_cb_arg; /* optional argument which might be of use in the CB */ const char *cb_errtext; /* used by the cb to display a specific error */ size_t max_length; /* Allocated length of the buffer PIN. */ char pin[1]; /* The buffer to hold the PIN or passphrase. It's actual allocated length is given by MAX_LENGTH (above). */ }; /* Types of the private keys. */ enum { PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */ PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */ PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */ PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard based key. */ PROTECTED_SHARED_SECRET = 4, /* RFU. */ PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */ }; /* Values for the cache_mode arguments. */ typedef enum { CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */ - CACHE_MODE_ANY, /* Any mode except ignore matches. */ + CACHE_MODE_ANY, /* Any mode except ignore and data matches. */ CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */ CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ CACHE_MODE_SSH, /* SSH related cache. */ - CACHE_MODE_NONCE /* This is a non-predictable nonce. */ + CACHE_MODE_NONCE, /* This is a non-predictable nonce. */ + CACHE_MODE_DATA /* Arbitrary data. */ } cache_mode_t; /* The TTL is seconds used for adding a new nonce mode cache item. */ #define CACHE_TTL_NONCE 120 /* The TTL in seconds used by the --preset option of some commands. This is the default value changeable by an OPTION command. */ #define CACHE_TTL_OPT_PRESET 900 /* The type of a function to lookup a TTL by a keygrip. */ typedef int (*lookup_ttl_t)(const char *hexgrip); /* This is a special version of the usual _() gettext macro. It assumes a server connection control variable with the name "ctrl" and uses that to translate a string according to the locale set for the connection. The macro LunderscoreIMPL is used by i18n to actually define the inline function when needed. */ #if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT) #define L_(a) agent_Lunderscore (ctrl, (a)) #define LunderscorePROTO \ static inline const char *agent_Lunderscore (ctrl_t ctrl, \ const char *string) \ GNUPG_GCC_ATTR_FORMAT_ARG(2); #define LunderscoreIMPL \ static inline const char * \ agent_Lunderscore (ctrl_t ctrl, const char *string) \ { \ return ctrl? i18n_localegettext (ctrl->lc_messages, string) \ /* */: gettext (string); \ } #else #define L_(a) (a) #endif /*-- gpg-agent.c --*/ void agent_exit (int rc) GPGRT_ATTR_NORETURN; /* Also implemented in other tools */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl); gpg_error_t agent_copy_startup_env (ctrl_t ctrl); const char *get_agent_socket_name (void); const char *get_agent_ssh_socket_name (void); int get_agent_active_connection_count (void); #ifdef HAVE_W32_SYSTEM void *get_agent_scd_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); /*-- command.c --*/ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra); gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) GPGRT_ATTR_SENTINEL(0); gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) GPGRT_ATTR_PRINTF(3,4); void bump_key_eventcounter (void); void bump_card_eventcounter (void); void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t); gpg_error_t pinentry_loopback (ctrl_t, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length); #ifdef HAVE_W32_SYSTEM int serve_mmapped_ssh_request (ctrl_t ctrl, unsigned char *request, size_t maxreqlen); #endif /*HAVE_W32_SYSTEM*/ /*-- command-ssh.c --*/ ssh_control_file_t ssh_open_control_file (void); void ssh_close_control_file (ssh_control_file_t cf); gpg_error_t ssh_read_control_file (ssh_control_file_t cf, char *r_hexgrip, int *r_disabled, int *r_ttl, int *r_confirm); gpg_error_t ssh_search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm); void start_command_handler_ssh (ctrl_t, gnupg_fd_t); /*-- findkey.c --*/ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result); int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force); gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, gcry_sexp_t *result, char **r_passphrase); gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); int agent_is_dsa_key (gcry_sexp_t s_key); int agent_is_eddsa_key (gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info); gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs); /*-- call-pinentry.c --*/ void initialize_module_call_pinentry (void); void agent_query_dump_state (void); void agent_reset_query (ctrl_t ctrl); int pinentry_active_p (ctrl_t ctrl, int waitseconds); gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *inital_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode); int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode); int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *notokay, int with_cancel); int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn); int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn); void agent_popup_message_stop (ctrl_t ctrl); int agent_clear_passphrase (ctrl_t ctrl, const char *keyinfo, cache_mode_t cache_mode); /*-- cache.c --*/ void initialize_module_cache (void); void deinitialize_module_cache (void); void agent_cache_housekeeping (void); void agent_flush_cache (void); int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, const char *data, int ttl); char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode); void agent_store_cache_hit (const char *key); /*-- pksign.c --*/ gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, gcry_sexp_t *signature_sexp, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, const void *overridedata, size_t overridedatalen); gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, membuf_t *outbuf, cache_mode_t cache_mode); /*-- pkdecrypt.c --*/ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *ciphertext, size_t ciphertextlen, membuf_t *outbuf, int *r_padding); /*-- genkey.c --*/ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, char **failed_constraint); gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase); int agent_genkey (ctrl_t ctrl, const char *cache_nonce, const char *keyparam, size_t keyparmlen, int no_protection, const char *override_passphrase, int preset, membuf_t *outbuf); gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr); /*-- protect.c --*/ unsigned long get_calibrated_s2k_count (void); unsigned long get_standard_s2k_count (void); unsigned char get_standard_s2k_count_rfc4880 (void); unsigned long get_standard_s2k_time (void); int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb); gpg_error_t agent_unprotect (ctrl_t ctrl, const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen); int agent_private_key_type (const unsigned char *privatekey); unsigned char *make_shadow_info (const char *serialno, const char *idstring); int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result); gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info); gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen); gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen); gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force); /*-- trustlist.c --*/ void initialize_module_trustlist (void); gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled); gpg_error_t agent_listtrusted (void *assuan_context); gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const char *desc_text, const unsigned char *digest, size_t digestlen, int algo, const unsigned char *shadow_info, unsigned char **r_sig, size_t *r_siglen); int divert_pkdecrypt (ctrl_t ctrl, const char *desc_text, const unsigned char *cipher, const unsigned char *shadow_info, char **r_buf, size_t *r_len, int *r_padding); int divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context); int divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, const char *keydata, size_t keydatalen); /*-- call-scd.c --*/ void initialize_module_call_scd (void); void agent_scd_dump_state (void); int agent_scd_check_running (void); void agent_scd_check_aliveness (void); int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg); int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand); int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, int mdalgo, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen); int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*,size_t), void *getpin_cb_arg, const char *desc_text, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen, int *r_padding); int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg); gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result); gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result); int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); /*-- learncard.c --*/ int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force); /*-- cvt-openpgp.c --*/ gpg_error_t extract_private_key (gcry_sexp_t s_key, int req_private_key_data, const char **r_algoname, int *r_npkey, int *r_nskey, const char **r_format, gcry_mpi_t *mpi_array, int arraysize, gcry_sexp_t *r_curve, gcry_sexp_t *r_flags); #endif /*AGENT_H*/ diff --git a/agent/cache.c b/agent/cache.c index 238b6e214..799d595ab 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -1,530 +1,544 @@ /* cache.c - keep a cache of passphrases * Copyright (C) 2002, 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include "agent.h" +/* The default TTL for DATA items. This has no configure + * option because it is expected that clients provide a TTL. */ +#define DEF_CACHE_TTL_DATA (10 * 60) /* 10 minutes. */ + /* The size of the encryption key in bytes. */ #define ENCRYPTION_KEYSIZE (128/8) /* A mutex used to serialize access to the cache. */ static npth_mutex_t cache_lock; /* The encryption context. This is the only place where the encryption key for all cached entries is available. It would be nice to keep this (or just the key) in some hardware device, for example a TPM. Libgcrypt could be extended to provide such a service. With the current scheme it is easy to retrieve the cached entries if access to Libgcrypt's memory is available. The encryption merely avoids grepping for clear texts in the memory. Nevertheless the encryption provides the necessary infrastructure to make it more secure. */ static gcry_cipher_hd_t encryption_handle; struct secret_data_s { int totallen; /* This includes the padding and space for AESWRAP. */ char data[1]; /* A string. */ }; +/* The cache object. */ typedef struct cache_item_s *ITEM; struct cache_item_s { ITEM next; time_t created; - time_t accessed; + time_t accessed; /* Not updated for CACHE_MODE_DATA */ int ttl; /* max. lifetime given in seconds, -1 one means infinite */ struct secret_data_s *pw; cache_mode_t cache_mode; int restricted; /* The value of ctrl->restricted is part of the key. */ char key[1]; }; /* The cache himself. */ static ITEM thecache; /* NULL or the last cache key stored by agent_store_cache_hit. */ static char *last_stored_cache_key; /* This function must be called once to initialize this module. It has to be done before a second thread is spawned. */ void initialize_module_cache (void) { int err; err = npth_mutex_init (&cache_lock, NULL); if (err) log_fatal ("error initializing cache module: %s\n", strerror (err)); } void deinitialize_module_cache (void) { gcry_cipher_close (encryption_handle); encryption_handle = NULL; } /* We do the encryption init on the fly. We can't do it in the module init code because that is run before we listen for connections and in case we are started on demand by gpg etc. it will only wait for a few seconds to decide whether the agent may now accept connections. Thus we should get into listen state as soon as possible. */ static gpg_error_t init_encryption (void) { gpg_error_t err; void *key; if (encryption_handle) return 0; /* Shortcut - Already initialized. */ err = gcry_cipher_open (&encryption_handle, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, GCRY_CIPHER_SECURE); if (!err) { key = gcry_random_bytes (ENCRYPTION_KEYSIZE, GCRY_STRONG_RANDOM); if (!key) err = gpg_error_from_syserror (); else { err = gcry_cipher_setkey (encryption_handle, key, ENCRYPTION_KEYSIZE); xfree (key); } if (err) { gcry_cipher_close (encryption_handle); encryption_handle = NULL; } } if (err) log_error ("error initializing cache encryption context: %s\n", gpg_strerror (err)); return err? gpg_error (GPG_ERR_NOT_INITIALIZED) : 0; } static void release_data (struct secret_data_s *data) { xfree (data); } static gpg_error_t new_data (const char *string, struct secret_data_s **r_data) { gpg_error_t err; struct secret_data_s *d, *d_enc; size_t length; int total; *r_data = NULL; err = init_encryption (); if (err) return err; length = strlen (string) + 1; /* We pad the data to 32 bytes so that it get more complicated finding something out by watching allocation patterns. This is usually not possible but we better assume nothing about our secure storage provider. To support the AESWRAP mode we need to add 8 extra bytes as well. */ total = (length + 8) + 32 - ((length+8) % 32); d = xtrymalloc_secure (sizeof *d + total - 1); if (!d) return gpg_error_from_syserror (); memcpy (d->data, string, length); d_enc = xtrymalloc (sizeof *d_enc + total - 1); if (!d_enc) { err = gpg_error_from_syserror (); xfree (d); return err; } d_enc->totallen = total; err = gcry_cipher_encrypt (encryption_handle, d_enc->data, total, d->data, total - 8); xfree (d); if (err) { xfree (d_enc); return err; } *r_data = d_enc; return 0; } /* Check whether there are items to expire. */ static void housekeeping (void) { ITEM r, rprev; time_t current = gnupg_get_time (); /* First expire the actual data */ for (r=thecache; r; r = r->next) { if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) { if (DBG_CACHE) log_debug (" expired '%s'.%d (%ds after last access)\n", r->key, r->restricted, r->ttl); release_data (r->pw); r->pw = NULL; r->accessed = current; } } - /* Second, make sure that we also remove them based on the created stamp so - that the user has to enter it from time to time. */ + /* Second, make sure that we also remove them based on the created + * stamp so that the user has to enter it from time to time. We + * don't do this for data items which are used to storage secrets in + * meory and are not user entered passphrases etc. */ for (r=thecache; r; r = r->next) { unsigned long maxttl; switch (r->cache_mode) { + case CACHE_MODE_DATA: + continue; /* No MAX TTL here. */ case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; default: maxttl = opt.max_cache_ttl; break; } if (r->pw && r->created + maxttl < current) { if (DBG_CACHE) log_debug (" expired '%s'.%d (%lus after creation)\n", r->key, r->restricted, opt.max_cache_ttl); release_data (r->pw); r->pw = NULL; r->accessed = current; } } /* Third, make sure that we don't have too many items in the list. * Expire old and unused entries after 30 minutes. */ for (rprev=NULL, r=thecache; r; ) { if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) { ITEM r2 = r->next; if (DBG_CACHE) log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", r->key, r->restricted, r->cache_mode); xfree (r); if (!rprev) thecache = r2; else rprev->next = r2; r = r2; } else { rprev = r; r = r->next; } } } void agent_cache_housekeeping (void) { int res; if (DBG_CACHE) log_debug ("agent_cache_housekeeping\n"); res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); housekeeping (); res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); } void agent_flush_cache (void) { ITEM r; int res; if (DBG_CACHE) log_debug ("agent_flush_cache\n"); res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); for (r=thecache; r; r = r->next) { if (r->pw) { if (DBG_CACHE) log_debug (" flushing '%s'.%d\n", r->key, r->restricted); release_data (r->pw); r->pw = NULL; r->accessed = 0; } } res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); } /* Compare two cache modes. */ static int cache_mode_equal (cache_mode_t a, cache_mode_t b) { /* CACHE_MODE_ANY matches any mode other than CACHE_MODE_IGNORE. */ - return ((a == CACHE_MODE_ANY && b != CACHE_MODE_IGNORE) - || (b == CACHE_MODE_ANY && a != CACHE_MODE_IGNORE) || a == b); + return ((a == CACHE_MODE_ANY + && !(b == CACHE_MODE_IGNORE || b == CACHE_MODE_DATA)) + || (b == CACHE_MODE_ANY + && !(a == CACHE_MODE_IGNORE || a == CACHE_MODE_DATA)) + || a == b); } /* Store the string DATA in the cache under KEY and mark it with a maximum lifetime of TTL seconds. If there is already data under this key, it will be replaced. Using a DATA of NULL deletes the entry. A TTL of 0 is replaced by the default TTL and a TTL of -1 set infinite timeout. CACHE_MODE is stored with the cache entry and used to select different timeouts. */ int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, const char *data, int ttl) { gpg_error_t err = 0; ITEM r; int res; int restricted = ctrl? ctrl->restricted : -1; res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); if (DBG_CACHE) log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n", key, restricted, cache_mode, ttl); housekeeping (); if (!ttl) { switch(cache_mode) { case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; + case CACHE_MODE_DATA: ttl = DEF_CACHE_TTL_DATA; break; default: ttl = opt.def_cache_ttl; break; } } if ((!ttl && data) || cache_mode == CACHE_MODE_IGNORE) goto out; for (r=thecache; r; r = r->next) { if (((cache_mode != CACHE_MODE_USER && cache_mode != CACHE_MODE_NONCE) || cache_mode_equal (r->cache_mode, cache_mode)) && r->restricted == restricted && !strcmp (r->key, key)) break; } if (r) /* Replace. */ { if (r->pw) { release_data (r->pw); r->pw = NULL; } if (data) { r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; r->cache_mode = cache_mode; err = new_data (data, &r->pw); if (err) log_error ("error replacing cache item: %s\n", gpg_strerror (err)); } } else if (data) /* Insert. */ { r = xtrycalloc (1, sizeof *r + strlen (key)); if (!r) err = gpg_error_from_syserror (); else { strcpy (r->key, key); r->restricted = restricted; r->created = r->accessed = gnupg_get_time (); r->ttl = ttl; r->cache_mode = cache_mode; err = new_data (data, &r->pw); if (err) xfree (r); else { r->next = thecache; thecache = r; } } if (err) log_error ("error inserting cache item: %s\n", gpg_strerror (err)); } out: res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); return err; } -/* Try to find an item in the cache. Note that we currently don't - make use of CACHE_MODE except for CACHE_MODE_NONCE and - CACHE_MODE_USER. */ +/* Try to find an item in the cache. */ char * agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) { gpg_error_t err; ITEM r; char *value = NULL; int res; int last_stored = 0; int restricted = ctrl? ctrl->restricted : -1; if (cache_mode == CACHE_MODE_IGNORE) return NULL; res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); if (!key) { key = last_stored_cache_key; if (!key) goto out; last_stored = 1; } if (DBG_CACHE) log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n", key, ctrl->restricted, cache_mode, last_stored? " (stored cache key)":""); housekeeping (); for (r=thecache; r; r = r->next) { if (r->pw && ((cache_mode != CACHE_MODE_USER && cache_mode != CACHE_MODE_NONCE) || cache_mode_equal (r->cache_mode, cache_mode)) && r->restricted == restricted && !strcmp (r->key, key)) { - /* Note: To avoid races KEY may not be accessed anymore below. */ - r->accessed = gnupg_get_time (); + /* Note: To avoid races KEY may not be accessed anymore + * below. Note also that we don't update the accessed time + * for data items. */ + if (r->cache_mode != CACHE_MODE_DATA) + r->accessed = gnupg_get_time (); if (DBG_CACHE) log_debug ("... hit\n"); if (r->pw->totallen < 32) err = gpg_error (GPG_ERR_INV_LENGTH); else if ((err = init_encryption ())) ; else if (!(value = xtrymalloc_secure (r->pw->totallen - 8))) err = gpg_error_from_syserror (); else { err = gcry_cipher_decrypt (encryption_handle, value, r->pw->totallen - 8, r->pw->data, r->pw->totallen); } if (err) { xfree (value); value = NULL; log_error ("retrieving cache entry '%s'.%d failed: %s\n", key, restricted, gpg_strerror (err)); } break; } } if (DBG_CACHE && value == NULL) log_debug ("... miss\n"); out: res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); return value; } /* Store the key for the last successful cache hit. That value is used by agent_get_cache if the requested KEY is given as NULL. NULL may be used to remove that key. */ void agent_store_cache_hit (const char *key) { char *new; char *old; /* To make sure the update is atomic under the non-preemptive thread * model, we must make sure not to surrender control to a different * thread. Therefore, we avoid calling the allocator during the * update. * * Background: xtrystrdup uses gcry_strdup which may use the secure * memory allocator of Libgcrypt. That allocator takes locks and * since version 1.14 libgpg-error is nPth aware and thus taking a * lock may now lead to thread switch. Note that this only happens * when secure memory is _allocated_ (the standard allocator uses * malloc which is not nPth aware) but not when calling _xfree_ * because gcry_free needs to check whether the pointer is in secure * memory and thus needs to take a lock. */ new = key ? xtrystrdup (key) : NULL; /* Atomic update. */ old = last_stored_cache_key; last_stored_cache_key = new; /* Done. */ xfree (old); } diff --git a/agent/command.c b/agent/command.c index 1a08cfcc0..925d1f780 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,3429 +1,3644 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001-2011 Free Software Foundation, Inc. * Copyright (C) 2001-2013 Werner Koch * Copyright (C) 2015 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* FIXME: we should not use the default assuan buffering but setup some buffering in secure mempory to protect session keys etc. */ #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include #include "../common/i18n.h" #include "cvt-openpgp.h" #include "../common/ssh-utils.h" #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum allowed size of the inquired ciphertext. */ #define MAXLEN_CIPHERTEXT 4096 /* Maximum allowed size of the key parameters. */ #define MAXLEN_KEYPARAM 1024 /* Maximum allowed size of key data as used in inquiries (bytes). */ #define MAXLEN_KEYDATA 8192 +/* Maximum length of a secret to store under one key. */ +#define MAXLEN_PUT_SECRET 4096 /* The size of the import/export KEK key (in bytes). */ #define KEYWRAP_KEYSIZE (128/8) /* A shortcut to call assuan_set_error using an gpg_err_code_t and a text string. */ #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Check that the maximum digest length we support has at least the length of the keygrip. */ #if MAX_DIGEST_LEN < 20 #error MAX_DIGEST_LEN shorter than keygrip #endif /* Data used to associate an Assuan context with local server data. This is this modules local part of the server_control_s struct. */ struct server_local_s { /* Our Assuan context. */ assuan_context_t assuan_ctx; /* If this flag is true, the passphrase cache is used for signing operations. It defaults to true but may be set on a per connection base. The global option opt.ignore_cache_for_signing takes precedence over this flag. */ unsigned int use_cache_for_signing : 1; /* Flag to suppress I/O logging during a command. */ unsigned int pause_io_logging : 1; /* Flag indicating that the connection is from ourselves. */ unsigned int connect_from_self : 1; /* Helper flag for io_monitor to allow suppressing of our own * greeting in some cases. See io_monitor for details. */ unsigned int greeting_seen : 1; /* If this flag is set to true the agent will be terminated after the end of the current session. */ unsigned int stopme : 1; /* Flag indicating whether pinentry notifications shall be done. */ unsigned int allow_pinentry_notify : 1; /* An allocated description for the next key operation. This is used if a pinnetry needs to be popped up. */ char *keydesc; /* Malloced KEK (Key-Encryption-Key) for the import_key command. */ void *import_key; /* Malloced KEK for the export_key command. */ void *export_key; /* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */ int allow_fully_canceled; /* Last CACHE_NONCE sent as status (malloced). */ char *last_cache_nonce; /* Last PASSWD_NONCE sent as status (malloced). */ char *last_passwd_nonce; }; /* An entry for the getval/putval commands. */ struct putval_item_s { struct putval_item_s *next; size_t off; /* Offset to the value into DATA. */ size_t len; /* Length of the value. */ char d[1]; /* Key | Nul | value. */ }; /* A list of key value pairs fpr the getval/putval commands. */ static struct putval_item_s *putval_list; /* To help polling clients, we keep track of the number of certain events. This structure keeps those counters. The counters are integers and there should be no problem if they are overflowing as callers need to check only whether a counter changed. The actual values are not meaningful. */ struct { /* Incremented if any of the other counters below changed. */ unsigned int any; /* Incremented if a key is added or removed from the internal privat key database. */ unsigned int key; /* Incremented if a change of the card readers stati has been detected. */ unsigned int card; } eventcounter; /* Local prototypes. */ static int command_has_option (const char *cmd, const char *cmdopt); /* Release the memory buffer MB but first wipe out the used memory. */ static void clear_outbuf (membuf_t *mb) { void *p; size_t n; p = get_membuf (mb, &n); if (p) { wipememory (p, n); xfree (p); } } /* Write the content of memory buffer MB as assuan data to CTX and wipe the buffer out afterwards. */ static gpg_error_t write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) { gpg_error_t ae; void *p; size_t n; p = get_membuf (mb, &n); if (!p) return out_of_core (); ae = assuan_send_data (ctx, p, n); memset (p, 0, n); xfree (p); return ae; } /* Clear the nonces used to enable the passphrase cache for certain multi-command command sequences. */ static void clear_nonce_cache (ctrl_t ctrl) { if (ctrl->server_local->last_cache_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = NULL; } if (ctrl->server_local->last_passwd_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = NULL; } } /* This function is called by Libassuan whenever the client sends a reset. It has been registered similar to the other Assuan commands. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; clear_nonce_cache (ctrl); return 0; } /* Replace all '+' by a blank in the string S. */ static void plus_to_blank (char *s) { for (; *s; s++) { if (*s == '+') *s = ' '; } } /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int parse_hexstring (assuan_context_t ctx, const char *string, size_t *len) { const char *p; size_t n; /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; if (*p != ' ' && *p != '\t' && *p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); *len = n; return 0; } /* Parse the keygrip in STRING into the provided buffer BUF. BUF must provide space for 20 bytes. BUF is not changed if the function returns an error. */ static int parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf) { int rc; size_t n = 0; rc = parse_hexstring (ctx, string, &n); if (rc) return rc; n /= 2; if (n != 20) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip"); if (hex2bin (string, buf, 20) < 0) return set_error (GPG_ERR_BUG, "hex2bin"); return 0; } +/* Parse the TTL from STRING. Leading and trailing spaces are + * skipped. The value is constrained to -1 .. MAXINT. On error 0 is + * returned, else the number of bytes scanned. */ +static size_t +parse_ttl (const char *string, int *r_ttl) +{ + const char *string_orig = string; + long ttl; + char *pend; + + ttl = strtol (string, &pend, 10); + string = pend; + if (string == string_orig || !(spacep (string) || !*string) + || ttl < -1L || (int)ttl != (long)ttl) + { + *r_ttl = 0; + return 0; + } + while (spacep (string) || *string== '\n') + string++; + *r_ttl = (int)ttl; + return string - string_orig; +} + + /* Write an Assuan status line. KEYWORD is the first item on the * status line. The following arguments are all separated by a space * in the output. The last argument must be a NULL. Linefeeds and * carriage returns characters (which are not allowed in an Assuan * status line) are silently quoted in C-style. */ gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); va_end (arg_ptr); return err; } /* This function is similar to print_assuan_status but takes a CTRL arg instead of an assuan context as first argument. */ gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } /* Helper to notify the client about a launched Pinentry. Because that might disturb some older clients, this is only done if enabled via an option. Returns an gpg error code. */ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra) { char line[256]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s", pid, extra?" ":"", extra? extra:""); return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } /* An agent progress callback for Libgcrypt. This has been registered * to be called via the progress dispatcher mechanism from * gpg-agent.c */ static void progress_cb (ctrl_t ctrl, const char *what, int printchar, int current, int total) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) ; else if (printchar == '\n' && what && !strcmp (what, "primegen")) agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what); else agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d", what, printchar=='\n'?'X':printchar, current, total); } /* Helper to print a message while leaving a command. Note that this * function does not call assuan_set_error; the caller may do this * prior to calling us. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; /* Not all users of gpg-agent know about the fully canceled error code; map it back if needed. */ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!ctrl->server_local->allow_fully_canceled) err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); } /* Most code from common/ does not know the error source, thus we fix this here. */ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN) err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err)); if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } static const char hlp_geteventcounter[] = "GETEVENTCOUNTER\n" "\n" "Return a status line named EVENTCOUNTER with the current values\n" "of all event counters. The values are decimal numbers in the range\n" "0 to UINT_MAX and wrapping around to 0. The actual values should\n" "not be relied upon, they shall only be used to detect a change.\n" "\n" "The currently defined counters are:\n" "\n" "ANY - Incremented with any change of any of the other counters.\n" "KEY - Incremented for added or removed private keys.\n" "CARD - Incremented for changes of the card readers stati."; static gpg_error_t cmd_geteventcounter (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u", eventcounter.any, eventcounter.key, eventcounter.card); } /* This function should be called once for all key removals or additions. This function is assured not to do any context switches. */ void bump_key_eventcounter (void) { eventcounter.key++; eventcounter.any++; } /* This function should be called for all card reader status changes. This function is assured not to do any context switches. */ void bump_card_eventcounter (void) { eventcounter.card++; eventcounter.any++; } static const char hlp_istrusted[] = "ISTRUSTED \n" "\n" "Return OK when we have an entry with this fingerprint in our\n" "trustlist"; static gpg_error_t cmd_istrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; /* Parse the fingerprint value. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; rc = agent_istrusted (ctrl, fpr, NULL); if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) return rc; else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF ) return gpg_error (GPG_ERR_NOT_TRUSTED); else return leave_cmd (ctx, rc); } static const char hlp_listtrusted[] = "LISTTRUSTED\n" "\n" "List all entries from the trustlist."; static gpg_error_t cmd_listtrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = agent_listtrusted (ctx); return leave_cmd (ctx, rc); } static const char hlp_martrusted[] = "MARKTRUSTED \n" "\n" "Store a new key in into the trustlist."; static gpg_error_t cmd_marktrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; int flag; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the fingerprint value */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (!spacep (p) || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; while (spacep (p)) p++; flag = *p++; if ( (flag != 'S' && flag != 'P') || !spacep (p) ) return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S"); while (spacep (p)) p++; rc = agent_marktrusted (ctrl, p, fpr, flag); return leave_cmd (ctx, rc); } static const char hlp_havekey[] = "HAVEKEY \n" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { gpg_error_t err; unsigned char buf[20]; do { err = parse_keygrip (ctx, line, buf); if (err) return err; if (!agent_key_available (buf)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') line++; while (*line == ' ' || *line == '\t') line++; } while (*line); /* No leave_cmd() here because errors are expected and would clutter the log. */ return gpg_error (GPG_ERR_NO_SECKEY); } static const char hlp_sigkey[] = "SIGKEY \n" "SETKEY \n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) return rc; ctrl->have_keygrip = 1; return 0; } static const char hlp_setkeydesc[] = "SETKEYDESC plus_percent_escaped_string\n" "\n" "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n" "or EXPORT_KEY operation if this operation requires a passphrase. If\n" "this command is not used a default text will be used. Note, that\n" "this description implictly selects the label used for the entry\n" "box; if the string contains the string PIN (which in general will\n" "not be translated), \"PIN\" is used, otherwise the translation of\n" "\"passphrase\" is used. The description string should not contain\n" "blanks unless they are percent or '+' escaped.\n" "\n" "The description is only valid for the next PKSIGN, PKDECRYPT,\n" "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation."; static gpg_error_t cmd_setkeydesc (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *desc, *p; for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage; we might late use it for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ plus_to_blank (desc); xfree (ctrl->server_local->keydesc); if (ctrl->restricted) { ctrl->server_local->keydesc = strconcat ((ctrl->restricted == 2 ? _("Note: Request from the web browser.") : _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL); } else ctrl->server_local->keydesc = xtrystrdup (desc); if (!ctrl->server_local->keydesc) return out_of_core (); return 0; } static const char hlp_sethash[] = "SETHASH (--hash=)|() \n" "\n" "The client can use this command to tell the server about the data\n" "(which usually is a hash) to be signed."; static gpg_error_t cmd_sethash (assuan_context_t ctx, char *line) { int rc; size_t n; char *p; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; /* Parse the alternative hash options which may be used instead of the algo number. */ if (has_option_name (line, "--hash")) { if (has_option (line, "--hash=sha1")) algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=rmd160")) algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=md5")) algo = GCRY_MD_MD5; else if (has_option (line, "--hash=tls-md5sha1")) algo = MD_USER_TLS_MD5SHA1; else return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); } else algo = 0; line = skip_options (line); if (!algo) { /* No hash option has been given: require an algo number instead */ algo = (int)strtoul (line, &endp, 10); for (line = endp; *line == ' ' || *line == '\t'; line++) ; if (!algo || gcry_md_test_algo (algo)) return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); } ctrl->digest.algo = algo; ctrl->digest.raw_value = 0; /* Parse the hash value. */ n = 0; rc = parse_hexstring (ctx, line, &n); if (rc) return rc; n /= 2; if (algo == MD_USER_TLS_MD5SHA1 && n == 36) ; else if (n != 16 && n != 20 && n != 24 && n != 28 && n != 32 && n != 48 && n != 64) return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); if (n > MAX_DIGEST_LEN) return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); buf = ctrl->digest.value; ctrl->digest.valuelen = n; for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); for (; n < ctrl->digest.valuelen; n++) buf[n] = 0; return 0; } static const char hlp_pksign[] = "PKSIGN [] []\n" "\n" "Perform the actual sign operation. Neither input nor output are\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { gpg_error_t err; cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; char *cache_nonce = NULL; char *p; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); if (opt.ignore_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc, &outbuf, cache_mode); if (err) clear_outbuf (&outbuf); else err = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_pkdecrypt[] = "PKDECRYPT []\n" "\n" "Perform the actual decrypt operation. Input is not\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; membuf_t outbuf; int padding; (void)line; /* First inquire the data to decrypt */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT); if (!rc) rc = assuan_inquire (ctx, "CIPHERTEXT", &value, &valuelen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, value, valuelen, &outbuf, &padding); xfree (value); if (rc) clear_outbuf (&outbuf); else { if (padding != -1) rc = print_assuan_status (ctx, "PADDING", "%d", padding); else rc = 0; if (!rc) rc = write_and_clear_outbuf (ctx, &outbuf); } xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, rc); } static const char hlp_genkey[] = "GENKEY [--no-protection] [--preset] [--inq-passwd]\n" " [--passwd-nonce=] []\n" "\n" "Generate a new key, store the secret part and return the public\n" "part. Here is an example transaction:\n" "\n" " C: GENKEY\n" " S: INQUIRE KEYPARAM\n" " C: D (genkey (rsa (nbits 3072)))\n" " C: END\n" " S: D (public-key\n" " S: D (rsa (n 326487324683264) (e 10001)))\n" " S: OK key created\n" "\n" "When the --preset option is used the passphrase for the generated\n" "key will be added to the cache. When --inq-passwd is used an inquire\n" "with the keyword NEWPASSWD is used to request the passphrase for the\n" "new key. When a --passwd-nonce is used, the corresponding cached\n" "passphrase is used to protect the new key."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; unsigned char *value; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; char *cache_nonce = NULL; char *passwd_nonce = NULL; int opt_preset; int opt_inq_passwd; size_t n; char *p, *pend; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); no_protection = has_option (line, "--no-protection"); opt_preset = has_option (line, "--preset"); opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { rc = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); /* First inquire the parameters */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM); if (!rc) rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; init_membuf (&outbuf, 512); /* If requested, ask for the password to be used for the key. If this is not used the regular Pinentry mechanism is used. */ if (opt_inq_passwd && !no_protection) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256); assuan_end_confidential (ctx); if (rc) goto leave; if (!*newpasswd) { /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; no_protection = 1; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); rc = agent_genkey (ctrl, cache_nonce, (char*)value, valuelen, no_protection, newpasswd, opt_preset, &outbuf); leave: if (newpasswd) { /* Assuan_inquire does not allow us to read into secure memory thus we need to wipe it ourself. */ wipememory (newpasswd, strlen (newpasswd)); xfree (newpasswd); } xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, rc); } static const char hlp_readkey[] = "READKEY \n" " --card \n" "\n" "Return the public key for the given keygrip or keyid.\n" "With --card, private key file with card information will be created."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_pkey = NULL; unsigned char *pkbuf = NULL; char *serialno = NULL; size_t pkbuflen; const char *opt_card; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_card = has_option_name (line, "--card"); line = skip_options (line); if (opt_card) { const char *keyid = opt_card; rc = agent_card_getattr (ctrl, "SERIALNO", &serialno); if (rc) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (rc)); goto leave; } rc = agent_card_readkey (ctrl, keyid, &pkbuf); if (rc) goto leave; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen); if (rc) goto leave; if (!gcry_pk_get_keygrip (s_pkey, grip)) { rc = gcry_pk_testkey (s_pkey); if (rc == 0) rc = gpg_error (GPG_ERR_INTERNAL); goto leave; } rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0); if (rc) goto leave; rc = assuan_send_data (ctx, pkbuf, pkbuflen); } else { rc = parse_keygrip (ctx, line, grip); if (rc) goto leave; rc = agent_public_key_from_file (ctrl, grip, &s_pkey); if (!rc) { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (pkbuflen); pkbuf = xtrymalloc (pkbuflen); if (!pkbuf) rc = gpg_error_from_syserror (); else { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, pkbuflen); rc = assuan_send_data (ctx, pkbuf, pkbuflen); } } } leave: xfree (serialno); xfree (pkbuf); gcry_sexp_release (s_pkey); return leave_cmd (ctx, rc); } static const char hlp_keyinfo[] = "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] \n" "\n" "Return information about the key specified by the KEYGRIP. If the\n" "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" "--list is given the keygrip is ignored and information about all\n" "available keys are returned. If --ssh-list is given information\n" "about all keys listed in the sshcontrol are returned. With --with-ssh\n" "information from sshcontrol is always added to the info. Unless --data\n" "is given, the information is returned as a status line using the format:\n" "\n" " KEYINFO \n" "\n" "KEYGRIP is the keygrip.\n" "\n" "TYPE is describes the type of the key:\n" " 'D' - Regular key stored on disk,\n" " 'T' - Key is stored on a smartcard (token),\n" " 'X' - Unknown type,\n" " '-' - Key is missing.\n" "\n" "SERIALNO is an ASCII string with the serial number of the\n" " smartcard. If the serial number is not known a single\n" " dash '-' is used instead.\n" "\n" "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n" " is not known a dash is used instead.\n" "\n" "CACHED is 1 if the passphrase for the key was found in the key cache.\n" " If not, a '-' is used instead.\n" "\n" "PROTECTION describes the key protection type:\n" " 'P' - The key is protected with a passphrase,\n" " 'C' - The key is not protected,\n" " '-' - Unknown protection.\n" "\n" "FPR returns the formatted ssh-style fingerprint of the key. It is only\n" " printed if the option --ssh-fpr has been used. It defaults to '-'.\n" "\n" "TTL is the TTL in seconds for that key or '-' if n/a.\n" "\n" "FLAGS is a word consisting of one-letter flags:\n" " 'D' - The key has been disabled,\n" " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n" " 'c' - Use of the key needs to be confirmed,\n" " '-' - No flags given.\n" "\n" "More information may be added in the future."; static gpg_error_t do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, int data, int with_ssh_fpr, int in_ssh, int ttl, int disabled, int confirm) { gpg_error_t err; char hexgrip[40+1]; char *fpr = NULL; int keytype; unsigned char *shadow_info = NULL; char *serialno = NULL; char *idstr = NULL; const char *keytypestr; const char *cached; const char *protectionstr; char *pw; int missing_key = 0; char ttlbuf[20]; char flagsbuf[5]; err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info); if (err) { if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) missing_key = 1; else goto leave; } /* Reformat the grip so that we use uppercase as good style. */ bin2hex (grip, 20, hexgrip); if (ttl > 0) snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl); else strcpy (ttlbuf, "-"); *flagsbuf = 0; if (disabled) strcat (flagsbuf, "D"); if (in_ssh) strcat (flagsbuf, "S"); if (confirm) strcat (flagsbuf, "c"); if (!*flagsbuf) strcpy (flagsbuf, "-"); if (missing_key) { protectionstr = "-"; keytypestr = "-"; } else { switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: protectionstr = "C"; keytypestr = "D"; break; case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; break; case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; break; default: protectionstr = "-"; keytypestr = "X"; break; } } /* Compute the ssh fingerprint if requested. */ if (with_ssh_fpr) { gcry_sexp_t key; if (!agent_raw_key_from_file (ctrl, grip, &key)) { ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr); gcry_sexp_release (key); } } /* Here we have a little race by doing the cache check separately from the retrieval function. Given that the cache flag is only a hint, it should not really matter. */ pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL); cached = pw ? "1" : "-"; xfree (pw); if (shadow_info) { err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); if (err) goto leave; } if (!data) err = agent_write_status (ctrl, "KEYINFO", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf, NULL); else { char *string; string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf); if (!string) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, string, strlen(string)); xfree (string); } leave: xfree (fpr); xfree (shadow_info); xfree (serialno); xfree (idstr); return err; } /* Entry int for the command KEYINFO. This function handles the command option processing. For details see hlp_keyinfo above. */ static gpg_error_t cmd_keyinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int err; unsigned char grip[20]; DIR *dir = NULL; int list_mode; int opt_data, opt_ssh_fpr, opt_with_ssh; ssh_control_file_t cf = NULL; char hexgrip[41]; int disabled, ttl, confirm, is_ssh; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--ssh-list")) list_mode = 2; else list_mode = has_option (line, "--list"); opt_data = has_option (line, "--data"); opt_ssh_fpr = has_option (line, "--ssh-fpr"); opt_with_ssh = has_option (line, "--with-ssh"); line = skip_options (line); if (opt_with_ssh || list_mode == 2) cf = ssh_open_control_file (); if (list_mode == 2) { if (cf) { while (!ssh_read_control_file (cf, hexgrip, &disabled, &ttl, &confirm)) { if (hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, ttl, disabled, confirm); if (err) goto leave; } } err = 0; } else if (list_mode) { char *dirname; struct dirent *dir_entry; dirname = make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } dir = opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); xfree (dirname); goto leave; } xfree (dirname); while ( (dir_entry = readdir (dir)) ) { if (strlen (dir_entry->d_name) != 44 || strcmp (dir_entry->d_name + 40, ".key")) continue; strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, hexgrip, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); if (err) goto leave; } err = 0; } else { err = parse_keygrip (ctx, line, grip); if (err) goto leave; disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, line, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); } leave: ssh_close_control_file (cf); if (dir) closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) leave_cmd (ctx, err); return err; } /* Helper for cmd_get_passphrase. */ static int send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw) { size_t n; int rc; assuan_begin_confidential (ctx); n = strlen (pw); if (via_data) rc = assuan_send_data (ctx, pw, n); else { char *p = xtrymalloc_secure (n*2+1); if (!p) rc = gpg_error_from_syserror (); else { bin2hex (pw, n, p); rc = assuan_set_okay_line (ctx, p); xfree (p); } } return rc; } static const char hlp_get_passphrase[] = "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n" " [--qualitybar] \n" " [ ]\n" "\n" "This function is usually used to ask for a passphrase to be used\n" "for conventional encryption, but may also be used by programs which\n" "need specal handling of passphrases. This command uses a syntax\n" "which helps clients to use the agent with minimum effort. The\n" "agent either returns with an error or with a OK followed by the hex\n" "encoded passphrase. Note that the length of the strings is\n" "implicitly limited by the maximum length of a command.\n" "\n" "If the option \"--data\" is used the passphrase is returned by usual\n" "data lines and not on the okay line.\n" "\n" "If the option \"--check\" is used the passphrase constraints checks as\n" "implemented by gpg-agent are applied. A check is not done if the\n" "passphrase has been found in the cache.\n" "\n" "If the option \"--no-ask\" is used and the passphrase is not in the\n" "cache the user will not be asked to enter a passphrase but the error\n" "code GPG_ERR_NO_DATA is returned. \n" "\n" "If the option \"--qualitybar\" is used a visual indication of the\n" "entered passphrase quality is shown. (Unless no minimum passphrase\n" "length has been configured.)"; static gpg_error_t cmd_get_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *pw; char *response; char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL; const char *desc2 = _("Please re-enter this passphrase"); char *p; int opt_data, opt_check, opt_no_ask, opt_qualbar; int opt_repeat = 0; char *entry_errtext = NULL; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_data = has_option (line, "--data"); opt_check = has_option (line, "--check"); opt_no_ask = has_option (line, "--no-ask"); if (has_option_name (line, "--repeat")) { p = option_value (line, "--repeat"); if (p) opt_repeat = atoi (p); else opt_repeat = 1; } opt_qualbar = has_option (line, "--qualitybar"); line = skip_options (line); cacheid = line; p = strchr (cacheid, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; errtext = p; p = strchr (errtext, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; prompt = p; p = strchr (prompt, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* Ignore trailing garbage. */ } } } if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); if (!desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (cacheid, "X")) cacheid = NULL; if (!strcmp (errtext, "X")) errtext = NULL; if (!strcmp (prompt, "X")) prompt = NULL; if (!strcmp (desc, "X")) desc = NULL; pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL; if (pw) { rc = send_back_passphrase (ctx, opt_data, pw); xfree (pw); } else if (opt_no_ask) rc = gpg_error (GPG_ERR_NO_DATA); else { /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (errtext) plus_to_blank (errtext); if (prompt) plus_to_blank (prompt); if (desc) plus_to_blank (desc); next_try: rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER); xfree (entry_errtext); entry_errtext = NULL; if (!rc) { int i; if (opt_check && check_passphrase_constraints (ctrl, response, &entry_errtext)) { xfree (response); goto next_try; } for (i = 0; i < opt_repeat; i++) { char *response2; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) break; rc = agent_get_passphrase (ctrl, &response2, desc2, prompt, errtext, 0, cacheid, CACHE_MODE_USER); if (rc) break; if (strcmp (response2, response)) { xfree (response2); xfree (response); entry_errtext = try_percent_escape (_("does not match - try again"), NULL); if (!entry_errtext) { rc = gpg_error_from_syserror (); break; } goto next_try; } xfree (response2); } if (!rc) { if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0); rc = send_back_passphrase (ctx, opt_data, response); } xfree (response); } } return leave_cmd (ctx, rc); } static const char hlp_clear_passphrase[] = "CLEAR_PASSPHRASE [--mode=normal] \n" "\n" "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" "added by the agent.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; int opt_normal; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_normal = has_option (line, "--mode=normal"); line = skip_options (line); /* parse the stuff */ for (p=line; *p == ' '; p++) ; cacheid = p; p = strchr (cacheid, ' '); if (p) *p = 0; /* ignore garbage */ if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); agent_put_cache (ctrl, cacheid, opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER, NULL, 0); agent_clear_passphrase (ctrl, cacheid, opt_normal ? CACHE_MODE_NORMAL : CACHE_MODE_USER); return 0; } static const char hlp_get_confirmation[] = "GET_CONFIRMATION \n" "\n" "This command may be used to ask for a simple confirmation.\n" "DESCRIPTION is displayed along with a Okay and Cancel button. This\n" "command uses a syntax which helps clients to use the agent with\n" "minimum effort. The agent either returns with an error or with a\n" "OK. Note, that the length of DESCRIPTION is implicitly limited by\n" "the maximum length of a command. DESCRIPTION should not contain\n" "any spaces, those must be encoded either percent escaped or simply\n" "as '+'."; static gpg_error_t cmd_get_confirmation (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the stuff */ for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage -may be later used for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (desc, "X")) desc = NULL; /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (desc) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); return leave_cmd (ctx, rc); } static const char hlp_learn[] = "LEARN [--send] [--sendinfo] [--force]\n" "\n" "Learn something about the currently inserted smartcard. With\n" "--sendinfo information about the card is returned; with --send\n" "the available certificates are returned as D lines; with --force\n" "private key storage will be updated by the result."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int send, sendinfo, force; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); return leave_cmd (ctx, err); } static const char hlp_passwd[] = "PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset]\n" " [--verify] \n" "\n" "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n" "--preset is used then the new passphrase will be added to the cache.\n" "If --verify is used the command asks for the passphrase and verifies\n" "that the passphrase valid.\n"; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int c; char *cache_nonce = NULL; char *passwd_nonce = NULL; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *shadow_info = NULL; char *passphrase = NULL; char *pend; int opt_preset, opt_verify; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_preset = has_option (line, "--preset"); cache_nonce = option_value (line, "--cache-nonce"); opt_verify = has_option (line, "--verify"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; ctrl->in_passwd++; err = agent_key_from_file (ctrl, opt_verify? NULL : cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, &passphrase); if (err) ; else if (shadow_info) { log_error ("changing a smartcard PIN is not yet supported\n"); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else if (opt_verify) { /* All done. */ if (passphrase) { if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } else { char *newpass = NULL; if (passwd_nonce) newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); err = agent_protect_and_store (ctrl, s_skey, &newpass); if (!err && passphrase) { /* A passphrase existed on the old key and the change was successful. Return a nonce for that old passphrase to let the caller try to unprotect the other subkeys with the same key. */ if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } if (newpass) { /* If we have a new passphrase (which might be empty) we store it under a passwd nonce so that the caller may send that nonce again to use it for another key. */ if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, newpass, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } if (!err && opt_preset) { char hexgrip[40+1]; bin2hex(grip, 20, hexgrip); err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass, ctrl->cache_ttl_opt_preset); } xfree (newpass); } ctrl->in_passwd--; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; leave: xfree (passphrase); gcry_sexp_release (s_skey); xfree (shadow_info); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, err); } static const char hlp_preset_passphrase[] = "PRESET_PASSPHRASE [--inquire] []\n" "\n" "Set the cached passphrase/PIN for the key identified by the keygrip\n" "to passwd for the given time, where -1 means infinite and 0 means\n" "the default (currently only a timeout of -1 is allowed, which means\n" "to never expire it). If passwd is not provided, ask for it via the\n" "pinentry module unless --inquire is passed in which case the passphrase\n" "is retrieved from the client via a server inquire.\n"; static gpg_error_t cmd_preset_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *grip_clear = NULL; unsigned char *passphrase = NULL; int ttl; size_t len; int opt_inquire; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!opt.allow_preset_passphrase) return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) return gpg_error (GPG_ERR_MISSING_VALUE); *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; /* Currently, only infinite timeouts are allowed. */ ttl = -1; if (line[0] != '-' || line[1] != '1') return gpg_error (GPG_ERR_NOT_IMPLEMENTED); line++; line++; while (!(*line != ' ' && *line != '\t')) line++; /* Syntax check the hexstring. */ len = 0; rc = parse_hexstring (ctx, line, &len); if (rc) return rc; line[len] = '\0'; /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) { if (opt_inquire) { rc = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and passphrase specified"); goto leave; } /* Do in-place conversion. */ passphrase = line; if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL)) rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); } else if (opt_inquire) { /* Note that the passphrase will be truncated at any null byte and the * limit is 480 characters. */ size_t maxlen = 480; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!rc) rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); } else rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required"); if (!rc) { rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (opt_inquire) xfree (passphrase); } leave: return leave_cmd (ctx, rc); } static const char hlp_scd[] = "SCD \n" " \n" "This is a general quote command to redirect everything to the\n" "SCdaemon."; static gpg_error_t cmd_scd (assuan_context_t ctx, char *line) { int rc; #ifdef BUILD_WITH_SCDAEMON ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = divert_generic_cmd (ctrl, line, ctx); #else (void)ctx; (void)line; rc = gpg_error (GPG_ERR_NOT_SUPPORTED); #endif return rc; } static const char hlp_keywrap_key[] = "KEYWRAP_KEY [--clear] \n" "\n" "Return a key to wrap another key. For now the key is returned\n" "verbatim and thus makes not much sense because an eavesdropper on\n" "the gpg-agent connection will see the key as well as the wrapped key.\n" "However, this function may either be equipped with a public key\n" "mechanism or not used at all if the key is a pre-shared key. In any\n" "case wrapping the import and export of keys is a requirement for\n" "certain cryptographic validations and thus useful. The key persists\n" "until a RESET command but may be cleared using the option --clear.\n" "\n" "Supported modes are:\n" " --import - Return a key to import a key into gpg-agent\n" " --export - Return a key to export a key from gpg-agent"; static gpg_error_t cmd_keywrap_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clearopt = has_option (line, "--clear"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); assuan_begin_confidential (ctx); if (has_option (line, "--import")) { xfree (ctrl->server_local->import_key); if (clearopt) ctrl->server_local->import_key = NULL; else if (!(ctrl->server_local->import_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); } else if (has_option (line, "--export")) { xfree (ctrl->server_local->export_key); if (clearopt) ctrl->server_local->export_key = NULL; else if (!(ctrl->server_local->export_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE"); assuan_end_confidential (ctx); return leave_cmd (ctx, err); } static const char hlp_import_key[] = "IMPORT_KEY [--unattended] [--force] []\n" "\n" "Import a secret key into the key store. The key is expected to be\n" "encrypted using the current session's key wrapping key (cf. command\n" "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" "key data. The unwrapped key must be a canonical S-expression. The\n" "option --unattended tries to import the key as-is without any\n" "re-encryption. Existing key can be overwritten with --force."; static gpg_error_t cmd_import_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int opt_unattended; int force; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *key = NULL; size_t keylen, realkeylen; char *passphrase = NULL; unsigned char *finalkey = NULL; size_t finalkeylen; unsigned char grip[20]; gcry_sexp_t openpgp_sexp = NULL; char *cache_nonce = NULL; char *p; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!ctrl->server_local->import_key) { err = gpg_error (GPG_ERR_MISSING_KEY); goto leave; } opt_unattended = has_option (line, "--unattended"); force = has_option (line, "--force"); line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); assuan_begin_confidential (ctx); err = assuan_inquire (ctx, "KEYDATA", &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (err) goto leave; if (wrappedkeylen < 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto leave; } keylen = wrappedkeylen - 8; key = xtrymalloc_secure (keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); if (err) goto leave; err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); if (err) goto leave; gcry_cipher_close (cipherhd); cipherhd = NULL; xfree (wrappedkey); wrappedkey = NULL; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ err = keygrip_from_canon_sexp (key, realkeylen, grip); if (err) { /* This might be due to an unsupported S-expression format. Check whether this is openpgp-private-key and trigger that import code. */ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen)) { const char *tag; size_t taglen; tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen); if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) ; else { gcry_sexp_release (openpgp_sexp); openpgp_sexp = NULL; } } if (!openpgp_sexp) goto leave; /* Note that ERR is still set. */ } if (openpgp_sexp) { /* In most cases the key is encrypted and thus the conversion function from the OpenPGP format to our internal format will ask for a passphrase. That passphrase will be returned and used to protect the key using the same code as for regular key import. */ xfree (key); key = NULL; err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip, ctrl->server_local->keydesc, cache_nonce, &key, opt_unattended? NULL : &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ if (passphrase) { assert (!opt_unattended); if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } else if (opt_unattended) { err = set_error (GPG_ERR_ASS_PARAMETER, "\"--unattended\" may only be used with OpenPGP keys"); goto leave; } else { if (!force && !agent_key_available (grip)) err = gpg_error (GPG_ERR_EEXIST); else { char *prompt = xtryasprintf (_("Please enter the passphrase to protect the " "imported object within the %s system."), GNUPG_NAME); if (!prompt) err = gpg_error_from_syserror (); else err = agent_ask_new_passphrase (ctrl, prompt, &passphrase); xfree (prompt); } if (err) goto leave; } if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count, -1); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, force); } else err = agent_write_private_key (grip, key, realkeylen, force); leave: gcry_sexp_release (openpgp_sexp); xfree (finalkey); xfree (passphrase); xfree (key); gcry_cipher_close (cipherhd); xfree (wrappedkey); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_export_key[] = "EXPORT_KEY [--cache-nonce=] [--openpgp] \n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n" "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" "compatible passphrase-protected form. Without --openpgp, the secret key\n" "material will be exported in the clear (after prompting the user to unlock\n" "it, if needed).\n"; static gpg_error_t cmd_export_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *key = NULL; size_t keylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; int openpgp; char *cache_nonce; char *passphrase = NULL; unsigned char *shadow_info = NULL; char *pend; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); openpgp = has_option (line, "--openpgp"); cache_nonce = option_value (line, "--cache-nonce"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); if (!ctrl->server_local->export_key) { err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?"); goto leave; } err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Get the key from the file. With the openpgp flag we also ask for the passphrase so that we can use it to re-encrypt it. */ err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, openpgp ? &passphrase : NULL); if (err) goto leave; if (shadow_info) { /* Key is on a smartcard. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } if (openpgp) { /* The openpgp option changes the key format into the OpenPGP key transfer format. The result is already a padded canonical S-expression. */ if (!passphrase) { err = agent_ask_new_passphrase (ctrl, _("This key (or subkey) is not protected with a passphrase." " Please enter a new passphrase to export it."), &passphrase); if (err) goto leave; } err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); if (!err && passphrase) { if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } } } else { /* Convert into a canonical S-expression and wrap that. */ err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); } if (err) goto leave; gcry_sexp_release (s_skey); s_skey = NULL; err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); if (err) goto leave; wrappedkeylen = keylen + 8; wrappedkey = xtrymalloc (wrappedkeylen); if (!wrappedkey) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); if (err) goto leave; xfree (key); key = NULL; gcry_cipher_close (cipherhd); cipherhd = NULL; assuan_begin_confidential (ctx); err = assuan_send_data (ctx, wrappedkey, wrappedkeylen); assuan_end_confidential (ctx); leave: xfree (cache_nonce); xfree (passphrase); xfree (wrappedkey); gcry_cipher_close (cipherhd); xfree (key); gcry_sexp_release (s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_delete_key[] = "DELETE_KEY [--force|--stub-only] \n" "\n" "Delete a secret key from the key store. If --force is used\n" "and a loopback pinentry is allowed, the agent will not ask\n" "the user for confirmation. If --stub-only is used the key will\n" "only be deleted if it is a reference to a token."; static gpg_error_t cmd_delete_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int force, stub_only; unsigned char grip[20]; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); stub_only = has_option (line, "--stub-only"); line = skip_options (line); /* If the use of a loopback pinentry has been disabled, we assume * that a silent deletion of keys shall also not be allowed. */ if (!opt.allow_loopback_pinentry) force = 0; err = parse_keygrip (ctx, line, grip); if (err) goto leave; err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force, stub_only); if (err) goto leave; leave: xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } #if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" #else #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" #endif static const char hlp_keytocard[] = "KEYTOCARD [--force] \n" "\n"; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; const char *serialno, *timestamp_str, *id; unsigned char *shadow_info = NULL; time_t timestamp; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Fixme: Replace the parsing code by split_fields(). */ line += 40; while (*line && (*line == ' ' || *line == '\t')) line++; serialno = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; id = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; timestamp_str = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (*line) *line = '\0'; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) { xfree (shadow_info); goto leave; } if (shadow_info) { /* Key is on a smartcard already. */ xfree (shadow_info); gcry_sexp_release (s_skey); err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); gcry_sexp_release (s_skey); goto leave; } gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); keydatalen--; /* Decrement for last '\0'. */ /* Add timestamp "created-at" in the private key */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); xfree (keydata); leave: return leave_cmd (ctx, err); } + +static const char hlp_get_secret[] = + "GET_SECRET \n" + "\n" + "Return the secret value stored under KEY\n"; +static gpg_error_t +cmd_get_secret (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + char *p, *key; + char *value = NULL; + size_t valuelen; + + /* For now we allow this only for local connections. */ + if (ctrl->restricted) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + line = skip_options (line); + + for (p=line; *p == ' '; p++) + ; + key = p; + p = strchr (key, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); + goto leave; + } + } + if (!*key) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); + goto leave; + } + + + value = agent_get_cache (ctrl, key, CACHE_MODE_DATA); + if (!value) + { + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + valuelen = percent_unescape_inplace (value, 0); + err = assuan_send_data (ctx, value, valuelen); + wipememory (value, valuelen); + + leave: + xfree (value); + return leave_cmd (ctx, err); +} + + +static const char hlp_put_secret[] = + "PUT_SECRET [--clear] []\n" + "\n" + "This commands stores a secret under KEY in gpg-agent's in-memory\n" + "cache. The TTL must be explicitly given by TTL and the options\n" + "from the configuration file are not used. The value is either given\n" + "percent-escaped as 3rd argument or if not given inquired by gpg-agent\n" + "using the keyword \"SECRET\".\n" + "The option --clear removes the secret from the cache." + ""; +static gpg_error_t +cmd_put_secret (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + int opt_clear; + unsigned char *value = NULL; + size_t valuelen = 0; + size_t n; + char *p, *key, *ttlstr; + unsigned char *valstr; + int ttl; + char *string = NULL; + + /* For now we allow this only for local connections. */ + if (ctrl->restricted) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + goto leave; + } + + opt_clear = has_option (line, "--clear"); + line = skip_options (line); + + for (p=line; *p == ' '; p++) + ; + key = p; + ttlstr = NULL; + valstr = NULL; + p = strchr (key, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + { + ttlstr = p; + p = strchr (ttlstr, ' '); + if (p) + { + *p++ = 0; + for (; *p == ' '; p++) + ; + if (*p) + valstr = p; + } + } + } + if (!*key) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no key given"); + goto leave; + } + if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl))) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given"); + goto leave; + } + if (valstr && opt_clear) + { + err = set_error (GPG_ERR_ASS_PARAMETER, + "value not expected with --clear"); + goto leave; + } + + if (valstr) + { + valuelen = percent_unescape_inplace (valstr, 0); + value = NULL; + } + else /* Inquire the value to store */ + { + err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET); + if (!err) + err = assuan_inquire (ctx, "SECRET", + &value, &valuelen, MAXLEN_PUT_SECRET); + if (err) + goto leave; + } + + /* Our cache expects strings and thus we need to turn the buffer + * into a string. Instead of resorting to base64 encoding we use a + * special percent escaping which only quoted the Nul and the + * percent character. */ + string = percent_data_escape (value? value : valstr, valuelen); + if (!string) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl); + + + leave: + if (string) + { + wipememory (string, strlen (string)); + xfree (string); + } + if (value) + { + wipememory (value, valuelen); + xfree (value); + } + return leave_cmd (ctx, err); +} + + static const char hlp_getval[] = "GETVAL \n" "\n" "Return the value for KEY from the special environment as created by\n" "PUTVAL."; static gpg_error_t cmd_getval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *p; struct putval_item_s *vl; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list; vl; vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Got an entry. */ rc = assuan_send_data (ctx, vl->d+vl->off, vl->len); else return gpg_error (GPG_ERR_NO_DATA); return leave_cmd (ctx, rc); } static const char hlp_putval[] = "PUTVAL []\n" "\n" "The gpg-agent maintains a kind of environment which may be used to\n" "store key/value pairs in it, so that they can be retrieved later.\n" "This may be used by helper daemons to daemonize themself on\n" "invocation and register them with gpg-agent. Callers of the\n" "daemon's service may now first try connect to get the information\n" "for that service from gpg-agent through the GETVAL command and then\n" "try to connect to that daemon. Only if that fails they may start\n" "an own instance of the service daemon. \n" "\n" "KEY is an arbitrary symbol with the same syntax rules as keys\n" "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n" "corresponding value; they should be similar to the values of\n" "envronment variables but gpg-agent does not enforce any\n" "restrictions. If that value is not given any value under that KEY\n" "is removed from this special environment."; static gpg_error_t cmd_putval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *value = NULL; size_t valuelen = 0; char *p; struct putval_item_s *vl, *vlprev; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { value = p; p = strchr (value, ' '); if (p) *p = 0; valuelen = percent_plus_unescape_inplace (value, 0); } } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Delete old entry. */ { if (vlprev) vlprev->next = vl->next; else putval_list = vl->next; xfree (vl); } if (valuelen) /* Add entry. */ { vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen); if (!vl) rc = gpg_error_from_syserror (); else { vl->len = valuelen; vl->off = strlen (key) + 1; strcpy (vl->d, key); memcpy (vl->d + vl->off, value, valuelen); vl->next = putval_list; putval_list = vl; } } return leave_cmd (ctx, rc); } static const char hlp_updatestartuptty[] = "UPDATESTARTUPTTY\n" "\n" "Set startup TTY and X11 DISPLAY variables to the values of this\n" "session. This command is useful to pull future pinentries to\n" "another screen. It is only required because there is no way in the\n" "ssh-agent protocol to convey this information."; static gpg_error_t cmd_updatestartuptty (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; session_env_t se; char *lc_ctype = NULL; char *lc_messages = NULL; int iterator; const char *name; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); se = session_env_new (); if (!se) err = gpg_error_from_syserror (); iterator = 0; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { const char *value = session_env_getenv (ctrl->session_env, name); if (value) err = session_env_setenv (se, name, value); } if (!err && ctrl->lc_ctype) if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype))) err = gpg_error_from_syserror (); if (!err && ctrl->lc_messages) if (!(lc_messages = xtrystrdup (ctrl->lc_messages))) err = gpg_error_from_syserror (); if (err) { session_env_release (se); xfree (lc_ctype); xfree (lc_messages); } else { session_env_release (opt.startup_env); opt.startup_env = se; xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = lc_ctype; xfree (opt.startup_lc_messages); opt.startup_lc_messages = lc_messages; } return err; } static const char hlp_killagent[] = "KILLAGENT\n" "\n" "Stop the agent."; static gpg_error_t cmd_killagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloadagent[] = "RELOADAGENT\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); agent_sighup_action (); return 0; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " ssh_socket_name - Return the name of the ssh socket.\n" " scd_running - Return OK if the SCdaemon is already running.\n" " s2k_time - Return the time in milliseconds required for S2K.\n" " s2k_count - Return the standard S2K count.\n" " s2k_count_cal - Return the calibrated S2K count.\n" " std_env_names - List the names of the standard environment.\n" " std_session_env - List the standard session environment.\n" " std_startup_env - List the standard startup environment.\n" " getenv NAME - Return value of envvar NAME.\n" " connections - Return number of active connections.\n" " jent_active - Returns OK if Libgcrypt's JENT is active.\n" " restricted - Returns OK if the connection is in restricted mode.\n" " cmd_has_option CMD OPT\n" " - Returns OK if command CMD has option OPT.\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_GENERAL); } } } } else if (!strcmp (line, "s2k_count")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_GENERAL); } else if (ctrl->restricted) { rc = gpg_error (GPG_ERR_FORBIDDEN); } /* All sub-commands below are not allowed in restricted mode. */ else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_agent_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "ssh_socket_name")) { const char *s = get_agent_ssh_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "scd_running")) { rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_GENERAL); } else if (!strcmp (line, "std_env_names")) { int iterator; const char *name; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { rc = assuan_send_data (ctx, name, strlen (name)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); if (rc) break; } } else if (!strcmp (line, "std_session_env") || !strcmp (line, "std_startup_env")) { int iterator; const char *name, *value; char *string; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { value = session_env_getenv_or_default (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL); if (value) { string = xtryasprintf ("%s=%s", name, value); if (!string) rc = gpg_error_from_syserror (); else { rc = assuan_send_data (ctx, string, strlen (string)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (rc) break; } } } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else rc = assuan_send_data (ctx, s, strlen (s)); } } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_agent_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "jent_active")) { #if GCRYPT_VERSION_NUMBER >= 0x010800 char *buf; char *fields[5]; buf = gcry_get_config (0, "rng-type"); if (buf && split_fields_colon (buf, fields, DIM (fields)) >= 5 && atoi (fields[4]) > 0) rc = 0; else rc = gpg_error (GPG_ERR_FALSE); gcry_free (buf); #else rc = gpg_error (GPG_ERR_FALSE); #endif } else if (!strcmp (line, "s2k_count_cal")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "s2k_time")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } /* This function is called by Libassuan to parse the OPTION command. It has been registered similar to the other Assuan commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "agent-awareness")) { /* The value is a version string telling us of which agent version the caller is aware of. */ ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); } /* All options below are not allowed in restricted mode. */ else if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (ctrl->session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (ctrl->session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = xtrystrdup (value); if (!ctrl->lc_ctype) return out_of_core (); } else if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "use-cache-for-signing")) ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0; else if (!strcmp (key, "allow-pinentry-notify")) ctrl->server_local->allow_pinentry_notify = 1; else if (!strcmp (key, "pinentry-mode")) { int tmp = parse_pinentry_mode (value); if (tmp == -1) err = gpg_error (GPG_ERR_INV_VALUE); else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else ctrl->pinentry_mode = tmp; } else if (!strcmp (key, "cache-ttl-opt-preset")) { ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0; } else if (!strcmp (key, "s2k-count")) { ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0; if (ctrl->s2k_count && ctrl->s2k_count < 65536) { ctrl->s2k_count = 0; } } else if (!strcmp (key, "pretend-request-origin")) { log_assert (!ctrl->restricted); switch (parse_request_origin (value)) { case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break; case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break; case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break; default: err = gpg_error (GPG_ERR_INV_VALUE); /* Better pretend to be remote in case of a bad value. */ ctrl->restricted = 1; break; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } /* Called by libassuan after all commands. ERR is the error from the last assuan operation and not the one returned from the command. */ static void post_cmd_notify (assuan_context_t ctx, gpg_error_t err) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)err; /* Switch off any I/O monitor controlled logging pausing. */ ctrl->server_local->pause_io_logging = 0; } /* This function is called by libassuan for all I/O. We use it here to disable logging for the GETEVENTCOUNTER commands. This is so that the debug output won't get cluttered by this primitive command. */ static unsigned int io_monitor (assuan_context_t ctx, void *hook, int direction, const char *line, size_t linelen) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) hook; /* We want to suppress all Assuan log messages for connections from * self. However, assuan_get_pid works only after * assuan_accept. Now, assuan_accept already logs a line ending with * the process id. We use this hack here to get the peers pid so * that we can compare it to our pid. We should add an assuan * function to return the pid for a file descriptor and use that to * detect connections to self. */ if (ctx && !ctrl->server_local->greeting_seen && direction == ASSUAN_IO_TO_PEER) { ctrl->server_local->greeting_seen = 1; if (linelen > 32 && !strncmp (line, "OK Pleased to meet you, process ", 32) && strtoul (line+32, NULL, 10) == getpid ()) return ASSUAN_IO_MONITOR_NOLOG; } /* Do not log self-connections. This makes the log cleaner because * we won't see the check-our-own-socket calls. */ if (ctx && ctrl->server_local->connect_from_self) return ASSUAN_IO_MONITOR_NOLOG; /* Note that we only check for the uppercase name. This allows the user to see the logging for debugging if using a non-upercase command name. */ if (ctx && direction == ASSUAN_IO_FROM_PEER && linelen >= 15 && !strncmp (line, "GETEVENTCOUNTER", 15) && (linelen == 15 || spacep (line+15))) { ctrl->server_local->pause_io_logging = 1; } return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "GET_PASSPHRASE")) { if (!strcmp (cmdopt, "repeat")) return 1; } return 0; } /* Tell Libassuan about our commands. Also register the other Assuan handlers. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter }, { "ISTRUSTED", cmd_istrusted, hlp_istrusted }, { "HAVEKEY", cmd_havekey, hlp_havekey }, { "KEYINFO", cmd_keyinfo, hlp_keyinfo }, { "SIGKEY", cmd_sigkey, hlp_sigkey }, { "SETKEY", cmd_sigkey, hlp_sigkey }, { "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc }, { "SETHASH", cmd_sethash, hlp_sethash }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt }, { "GENKEY", cmd_genkey, hlp_genkey }, { "READKEY", cmd_readkey, hlp_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted }, { "MARKTRUSTED", cmd_marktrusted, hlp_martrusted }, { "LEARN", cmd_learn, hlp_learn }, { "PASSWD", cmd_passwd, hlp_passwd }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd, hlp_scd }, { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key }, { "IMPORT_KEY", cmd_import_key, hlp_import_key }, { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "DELETE_KEY", cmd_delete_key, hlp_delete_key }, + { "GET_SECRET", cmd_get_secret, hlp_get_secret }, + { "PUT_SECRET", cmd_put_secret, hlp_put_secret }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty }, { "KILLAGENT", cmd_killagent, hlp_killagent }, { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_register_post_cmd_notify (ctx, post_cmd_notify); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple piper server, otherwise it is a regular server. CTRL is the control structure for this connection; it has only the basic initialization. */ void start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) { int rc; assuan_context_t ctx = NULL; if (ctrl->restricted) { if (agent_copy_startup_env (ctrl)) return; } rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); agent_exit (2); } if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else if (listen_fd != GNUPG_INVALID_FD) { rc = assuan_init_socket_server (ctx, listen_fd, 0); /* FIXME: Need to call assuan_sock_set_nonce for Windows. But this branch is currently not used. */ } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); agent_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); agent_exit (2); } assuan_set_pointer (ctx, ctrl); ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->assuan_ctx = ctx; ctrl->server_local->use_cache_for_signing = 1; ctrl->digest.raw_value = 0; assuan_set_io_monitor (ctx, io_monitor, NULL); agent_set_progress_cb (progress_cb, ctrl); for (;;) { - assuan_peercred_t client_creds; + assuan_peercred_t client_creds; /* Note: Points into CTX. */ + pid_t pid; rc = assuan_accept (ctx); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_get_peercred (ctx, &client_creds); if (rc) { - log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc)); - client_creds->pid = assuan_get_pid (ctx); + + if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) + ; + else + log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc)); + pid = assuan_get_pid (ctx); ctrl->client_uid = -1; } - ctrl->server_local->connect_from_self = - (client_creds->pid == getpid ()); - if (client_creds->pid != ASSUAN_INVALID_PID) - ctrl->client_pid = (unsigned long)client_creds->pid; else - ctrl->client_pid = 0; - ctrl->client_uid = client_creds->uid; + { + pid = client_creds->pid; + ctrl->client_uid = client_creds->uid; + } + ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid; + ctrl->server_local->connect_from_self = (pid == getpid ()); rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Reset the nonce caches. */ clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ agent_reset_scd (ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (ctrl); /* Cleanup. */ assuan_release (ctx); xfree (ctrl->server_local->keydesc); xfree (ctrl->server_local->import_key); xfree (ctrl->server_local->export_key); if (ctrl->server_local->stopme) agent_exit (0); xfree (ctrl->server_local); ctrl->server_local = NULL; } /* Helper for the pinentry loopback mode. It merely passes the parameters on to the client. */ gpg_error_t pinentry_loopback(ctrl_t ctrl, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length) { gpg_error_t rc; assuan_context_t ctx = ctrl->server_local->assuan_ctx; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length); if (rc) return rc; assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, keyword, buffer, size, max_length); assuan_end_confidential (ctx); return rc; } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index bd9a471e8..1fdc94d0f 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,3258 +1,3258 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc. * Copyright (C) 2000-2016 Werner Koch * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # ifndef WINVER # define WINVER 0x0500 /* Same as in common/sysutils.c */ # endif # ifdef HAVE_WINSOCK2_H # include # endif # include # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #ifdef HAVE_SIGNAL_H # include #endif #include #define GNUPG_COMMON_NEED_AFLOCAL #include "agent.h" #include /* Malloc hooks and socket wrappers. */ #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/exechelp.h" #include "../common/asshelp.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, aGPGConfTest, aUseStandardSocketP, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oDebugQuickRandom, oDebugPinentry, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oGrab, oNoGrab, oLogFile, oServer, oDaemon, oSupervised, oBatch, oPinentryProgram, oPinentryTouchFile, oPinentryInvisibleChar, oPinentryTimeout, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oScdaemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, oEnforcePassphraseConstraints, oMinPassphraseLen, oMinPassphraseNonalpha, oCheckPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, oEnableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, oExtraSocket, oBrowserSocket, oFakedSystemTime, oIgnoreCacheForSigning, oAllowMarkTrusted, oNoAllowMarkTrusted, oAllowPresetPassphrase, oAllowLoopbackPinentry, oNoAllowLoopbackPinentry, oNoAllowExternalCache, oAllowEmacsPinentry, oKeepTTY, oKeepDISPLAY, oSSHSupport, oSSHFingerprintDigest, oPuttySupport, oDisableScdaemon, oDisableCheckOwnSocket, oS2KCount, oAutoExpandSecmem, oListenBacklog, oWriteEnvFile }; #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif static ARGPARSE_OPTS opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"), ARGPARSE_group (301, N_("@Options:\n ")), ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), #ifndef HAVE_W32_SYSTEM ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")), #endif ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oGrab, "grab", "@"), /* FIXME: Add the below string for 2.3 */ /* N_("let PIN-Entry grab keyboard and mouse")), */ ARGPARSE_s_n (oNoGrab, "no-grab", "@"), ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), ARGPARSE_s_s (oPinentryProgram, "pinentry-program", /* */ N_("|PGM|use PGM as the PIN-Entry program")), ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"), ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"), ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oExtraSocket, "extra-socket", /* */ N_("|NAME|accept some commands via NAME")), ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_n (oKeepTTY, "keep-tty", /* */ N_("ignore requests to change the TTY")), ARGPARSE_s_n (oKeepDISPLAY, "keep-display", /* */ N_("ignore requests to change the X display")), ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl", N_("|N|expire cached PINs after N seconds")), ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ), ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ), ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ), ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints", /* */ "@"), ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"), ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"), ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"), ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"), ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"), ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing", /* */ N_("do not use the PIN cache when signing")), ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache", /* */ N_("disallow the use of an external password cache")), ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted", /* */ N_("disallow clients to mark keys as \"trusted\"")), ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"), ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase", /* */ N_("allow presetting passphrase")), ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry", N_("disallow caller to override the pinentry")), ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"), ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry", /* */ N_("allow passphrase to be prompted through Emacs")), ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")), ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest", N_("|ALGO|use ALGO to show ssh fingerprints")), ARGPARSE_s_n (oPuttySupport, "enable-putty-support", #ifdef HAVE_W32_SYSTEM /* */ N_("enable putty support") #else /* */ "@" #endif ), ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), ARGPARSE_s_u (oS2KCount, "s2k-count", "@"), ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"), ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), /* Dummy options for backward compatibility. */ ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"), ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), ARGPARSE_end () /* End of list */ }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ #define MAX_CACHE_TTL (120*60) /* 2 hours */ #define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */ #define MIN_PASSPHRASE_LEN (8) #define MIN_PASSPHRASE_NONALPHA (1) #define MAX_PASSPHRASE_DAYS (0) /* The timer tick used for housekeeping stuff. Note that on Windows * we use a SetWaitableTimer seems to signal earlier than about 2 * seconds. Thus we use 4 seconds on all platforms except for * Windowsce. CHECK_OWN_SOCKET_INTERVAL defines how often we check * our own socket in standard socket mode. If that value is 0 we * don't check at all. All values are in seconds. */ #if defined(HAVE_W32CE_SYSTEM) # define TIMERTICK_INTERVAL (60) # define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ #else # define TIMERTICK_INTERVAL (4) # define CHECK_OWN_SOCKET_INTERVAL (60) #endif /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; #ifdef HAVE_W32_SYSTEM /* Flag indicating that support for Putty has been enabled. */ static int putty_support; /* A magic value used with WM_COPYDATA. */ #define PUTTY_IPC_MAGIC 0x804e50ba /* To avoid surprises we limit the size of the mapped IPC file to this value. Putty currently (0.62) uses 8k, thus 16k should be enough for the foreseeable future. */ #define PUTTY_IPC_MAXLEN 16384 #endif /*HAVE_W32_SYSTEM*/ /* The list of open file descriptors at startup. Note that this list * has been allocated using the standard malloc. */ #ifndef HAVE_W32_SYSTEM static int *startup_fd_list; #endif /* The signal mask at startup and a flag telling whether it is valid. */ #ifdef HAVE_SIGPROCMASK static sigset_t startup_signal_mask; static int startup_signal_mask_valid; #endif /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; /* Counter for the currently running own socket checks. */ static int check_own_socket_running; /* Flags to indicate that check_own_socket shall not be called. */ static int disable_check_own_socket; /* Flag indicating that we are in supervised mode. */ static int is_supervised; /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; /* It is possible that we are currently running under setuid permissions */ static int maybe_setuid = 1; /* Name of the communication socket used for native gpg-agent requests. The second variable is either NULL or a malloced string with the real socket name in case it has been redirected. */ static char *socket_name; static char *redir_socket_name; /* Name of the optional extra socket used for native gpg-agent requests. */ static char *socket_name_extra; static char *redir_socket_name_extra; /* Name of the optional browser socket used for native gpg-agent requests. */ static char *socket_name_browser; static char *redir_socket_name_browser; /* Name of the communication socket used for ssh-agent protocol. */ static char *socket_name_ssh; static char *redir_socket_name_ssh; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; static assuan_sock_nonce_t socket_nonce_extra; static assuan_sock_nonce_t socket_nonce_browser; static assuan_sock_nonce_t socket_nonce_ssh; /* Value for the listen() backlog argument. We use the same value for * all sockets - 64 is on current Linux half of the default maximum. * Let's try this as default. Change at runtime with --listen-backlog. */ static int listen_backlog = 64; /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; static char *default_ttytype; static char *default_lc_ctype; static char *default_lc_messages; static char *default_xauthority; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ static char *config_filename; /* Helper to implement --debug-level */ static const char *debug_level; /* Keep track of the current log file so that we can avoid updating the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; /* The handle_tick() function may test whether a parent is still * running. We record the PID of the parent here or -1 if it should * be watched. */ static pid_t parent_pid = (pid_t)(-1); /* This flag is true if the inotify mechanism for detecting the * removal of the homedir is active. This flag is used to disable the * alternative but portable stat based check. */ static int have_homedir_inotify; /* Depending on how gpg-agent was started, the homedir inotify watch * may not be reliable. This flag is set if we assume that inotify * works reliable. */ static int reliable_homedir_inotify; /* Number of active connections. */ static int active_connections; /* This object is used to dispatch progress messages from Libgcrypt to * the right thread. Given that we will have at max only a few dozen * connections at a time, using a linked list is the easiest way to * handle this. */ struct progress_dispatch_s { struct progress_dispatch_s *next; /* The control object of the connection. If this is NULL no * connection is associated with this item and it is free for reuse * by new connections. */ ctrl_t ctrl; /* The thread id of (npth_self) of the connection. */ npth_t tid; /* The callback set by the connection. This is similar to the * Libgcrypt callback but with the control object passed as the * first argument. */ void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total); }; struct progress_dispatch_s *progress_dispatch_list; /* Local prototypes. */ static char *create_socket_name (char *standard_name, int with_homedir); static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce); static void create_directories (void); static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total); static void agent_init_default_ctrl (ctrl_t ctrl); static void agent_deinit_default_ctrl (ctrl_t ctrl); static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh); static void check_own_socket (void); static int check_for_running_agent (int silent); /* Pth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; /* Functions. */ /* Allocate a string describing a library version by calling a GETFNC. This function is expected to be called only once. GETFNC is expected to have a semantic like gcry_check_version (). */ static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } /* Return strings describing this program. The case values are described in common/argparse.c:strusage. The values here override the default values given by strusage. */ static const char * my_strusage (int level) { static char *ver_gcry; const char *p; switch (level) { case 11: p = "@GPG_AGENT@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 1: case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n" "Secret key management for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. Note that we don't fail here, because it is important to keep gpg-agent running even after re-reading the options due to a SIGHUP. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } /* Helper for cleanup to remove one socket with NAME. REDIR_NAME is the corresponding real name if the socket has been redirected. */ static void remove_socket (char *name, char *redir_name) { if (name && *name) { if (redir_name) name = redir_name; gnupg_remove (name); *name = 0; } } /* Discover which inherited file descriptors correspond to which * services/sockets offered by gpg-agent, using the LISTEN_FDS and * LISTEN_FDNAMES convention. The understood labels are "ssh", * "extra", and "browser". "std" or other labels will be interpreted * as the standard socket. * * This function is designed to log errors when the expected file * descriptors don't make sense, but to do its best to continue to * work even in the face of minor misconfigurations. * * For more information on the LISTEN_FDS convention, see * sd_listen_fds(3) on certain Linux distributions. */ #ifndef HAVE_W32_SYSTEM static void map_supervised_sockets (gnupg_fd_t *r_fd, gnupg_fd_t *r_fd_extra, gnupg_fd_t *r_fd_browser, gnupg_fd_t *r_fd_ssh) { struct { const char *label; int **fdaddr; char **nameaddr; } tbl[] = { { "ssh", &r_fd_ssh, &socket_name_ssh }, { "browser", &r_fd_browser, &socket_name_browser }, { "extra", &r_fd_extra, &socket_name_extra }, { "std", &r_fd, &socket_name } /* (Must be the last item.) */ }; const char *envvar; char **fdnames; int nfdnames; int fd_count; *r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1; /* Print a warning if LISTEN_PID does not match outr pid. */ envvar = getenv ("LISTEN_PID"); if (!envvar) log_error ("no LISTEN_PID environment variable found in " "--supervised mode (ignoring)\n"); else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ()) log_error ("environment variable LISTEN_PID (%lu) does not match" " our pid (%lu) in --supervised mode (ignoring)\n", (unsigned long)strtoul (envvar, NULL, 10), (unsigned long)getpid ()); /* Parse LISTEN_FDNAMES into the array FDNAMES. */ envvar = getenv ("LISTEN_FDNAMES"); if (envvar) { fdnames = strtokenize (envvar, ":"); if (!fdnames) { log_error ("strtokenize failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); agent_exit (1); } for (nfdnames=0; fdnames[nfdnames]; nfdnames++) ; } else { fdnames = NULL; nfdnames = 0; } /* Parse LISTEN_FDS into fd_count or provide a replacement. */ envvar = getenv ("LISTEN_FDS"); if (envvar) fd_count = atoi (envvar); else if (fdnames) { log_error ("no LISTEN_FDS environment variable found in --supervised" " mode (relying on LISTEN_FDNAMES instead)\n"); fd_count = nfdnames; } else { log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables " "found in --supervised mode" " (assuming 1 active descriptor)\n"); fd_count = 1; } if (fd_count < 1) { log_error ("--supervised mode expects at least one file descriptor" " (was told %d, carrying on as though it were 1)\n", fd_count); fd_count = 1; } /* Assign the descriptors to the return values. */ if (!fdnames) { struct stat statbuf; if (fd_count != 1) log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1" " in --supervised mode." " (ignoring all sockets but the first one)\n", fd_count); if (fstat (3, &statbuf) == -1 && errno ==EBADF) log_fatal ("file descriptor 3 must be valid in --supervised mode" " if LISTEN_FDNAMES is not set\n"); *r_fd = 3; socket_name = gnupg_get_socket_name (3); } else if (fd_count != nfdnames) { log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match " "LISTEN_FDS (%d) in --supervised mode\n", nfdnames, fd_count); } else { int i, j, fd; char *name; for (i = 0; i < nfdnames; i++) { for (j = 0; j < DIM (tbl); j++) { if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1) { fd = 3 + i; if (**tbl[j].fdaddr == -1) { name = gnupg_get_socket_name (fd); if (name) { **tbl[j].fdaddr = fd; *tbl[j].nameaddr = name; log_info ("using fd %d for %s socket (%s)\n", fd, tbl[j].label, name); } else { log_error ("cannot listen on fd %d for %s socket\n", fd, tbl[j].label); close (fd); } } else { log_error ("cannot listen on more than one %s socket\n", tbl[j].label); close (fd); } break; } } } } xfree (fdnames); } #endif /*!HAVE_W32_SYSTEM*/ /* Cleanup code for this program. This is either called has an atexit handler or directly. */ static void cleanup (void) { static int done; if (done) return; done = 1; deinitialize_module_cache (); if (!is_supervised && !inhibit_socket_removal) { remove_socket (socket_name, redir_socket_name); if (opt.extra_socket > 1) remove_socket (socket_name_extra, redir_socket_name_extra); if (opt.browser_socket > 1) remove_socket (socket_name_browser, redir_socket_name_browser); remove_socket (socket_name_ssh, redir_socket_name_ssh); } } /* Handle options which are allowed to be reset after program start. Return true when the current option in PARGS could be handled and false if not. As a special feature, passing a value of NULL for PARGS, resets the options to the default. REREAD should be set true if it is not the initial option parsing. */ static int parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) { int i; if (!pargs) { /* reset mode */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.no_grab = 1; opt.debug_pinentry = 0; opt.pinentry_program = NULL; opt.pinentry_touch_file = NULL; xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = NULL; opt.pinentry_timeout = 0; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH; opt.enforce_passphrase_constraints = 0; opt.min_passphrase_len = MIN_PASSPHRASE_LEN; opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA; opt.check_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; opt.enable_extended_key_format = 0; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.allow_external_cache = 1; opt.allow_loopback_pinentry = 1; opt.allow_emacs_pinentry = 0; opt.disable_scdaemon = 0; disable_check_own_socket = 0; /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_MD5; opt.s2k_count = 0; return 1; } switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs->r.ret_str; break; case oDebugPinentry: opt.debug_pinentry = 1; break; case oLogFile: if (!reread) return 0; /* not handeld */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { log_set_file (pargs->r.ret_str); xfree (current_logfile); current_logfile = xtrystrdup (pargs->r.ret_str); } break; case oNoGrab: opt.no_grab |= 1; break; case oGrab: opt.no_grab |= 2; break; case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; case oPinentryInvisibleChar: xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_scdaemon = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; case oEnforcePassphraseConstraints: opt.enforce_passphrase_constraints=1; break; case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break; case oMinPassphraseNonalpha: opt.min_passphrase_nonalpha = pargs->r.ret_ulong; break; case oCheckPassphrasePattern: opt.check_passphrase_pattern = pargs->r.ret_str; break; case oMaxPassphraseDays: opt.max_passphrase_days = pargs->r.ret_ulong; break; case oEnablePassphraseHistory: opt.enable_passphrase_history = 1; break; case oEnableExtendedKeyFormat: opt.enable_extended_key_format = 1; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break; case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break; case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break; case oNoAllowExternalCache: opt.allow_external_cache = 0; break; case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1; break; case oSSHFingerprintDigest: i = gcry_md_map_name (pargs->r.ret_str); if (!i) log_error (_("selected digest algorithm is invalid\n")); else opt.ssh_fingerprint_digest = i; break; case oS2KCount: opt.s2k_count = pargs->r.ret_ulong; break; default: return 0; /* not handled */ } return 1; /* handled */ } /* Fixup some options after all have been processed. */ static void finalize_rereadable_options (void) { /* Hack to allow --grab to override --no-grab. */ if ((opt.no_grab & 2)) opt.no_grab = 0; } static void thread_init_once (void) { static int npth_initialized = 0; if (!npth_initialized) { npth_initialized++; npth_init (); } gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Now that we have set the syscall clamp we need to tell Libgcrypt * that it should get them from libgpg-error. Note that Libgcrypt * has already been initialized but at that point nPth was not * initialized and thus Libgcrypt could not set its system call * clamp. */ #if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); #endif } static void initialize_modules (void) { thread_init_once (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_call_scd (); initialize_module_trustlist (); } /* The main entry point. */ int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int pipe_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; int debug_wait = 0; int gpgconf_list = 0; gpg_error_t err; struct assuan_malloc_hooks malloc_hooks; early_system_init (); /* Before we do anything else we save the list of currently open file descriptors and the signal mask. This info is required to do the exec call properly. We don't need it on Windows. */ #ifndef HAVE_W32_SYSTEM startup_fd_list = get_all_open_fds (); #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_SIGPROCMASK if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) startup_signal_mask_valid = 1; #endif /*HAVE_SIGPROCMASK*/ /* Set program name etc. */ set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to INIT_SECMEM() somewhere after the option parsing */ log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_sock_init (); assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL); disable_core_dumps (); /* Set default options. */ parse_rereadable_options (NULL, 0); /* Reset them to default values. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Record some of the original environment strings. */ { const char *s; int idx; static const char *names[] = { "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL }; err = 0; opt.startup_env = session_env_new (); if (!opt.startup_env) err = gpg_error_from_syserror (); for (idx=0; !err && names[idx]; idx++) { s = getenv (names[idx]); if (s) err = session_env_setenv (opt.startup_env, names[idx], s); } if (!err) { s = gnupg_ttyname (0); if (s) err = session_env_setenv (opt.startup_env, "GPG_TTY", s); } if (err) log_fatal ("error recording startup environment: %s\n", gpg_strerror (err)); /* Fixme: Better use the locale function here. */ opt.startup_lc_ctype = getenv ("LC_CTYPE"); if (opt.startup_lc_ctype) opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); opt.startup_lc_messages = getenv ("LC_MESSAGES"); if (opt.startup_lc_messages) opt.startup_lc_messages = xstrdup (opt.startup_lc_messages); } /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) gnupg_set_homedir (pargs.r.ret_str); else if (pargs.r_opt == oDebugQuickRandom) { gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); } } /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ if (default_config) configname = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); /* Save the default conf file name so that reread_configuration is able to test whether the config file has been created in the meantime. */ xfree (config_filename); config_filename = configname; configname = NULL; } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case aGPGConfTest: gpgconf_list = 2; break; case aUseStandardSocketP: gpgconf_list = 3; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: /* Dummy option. */ break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; break; case oSupervised: is_supervised = 1; break; case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break; case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str); break; case oUseStandardSocket: case oNoUseStandardSocket: obsolete_option (configname, configlineno, "use-standard-socket"); break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: ssh_support = 1; break; case oPuttySupport: # ifdef HAVE_W32_SYSTEM putty_support = 1; # endif break; case oExtraSocket: opt.extra_socket = 1; /* (1 = points into argv) */ socket_name_extra = pargs.r.ret_str; break; case oBrowserSocket: opt.browser_socket = 1; /* (1 = points into argv) */ socket_name_browser = pargs.r.ret_str; break; case oAutoExpandSecmem: /* Try to enable this option. It will officially only be * supported by Libgcrypt 1.9 but 1.8.2 already supports it * on the quiet and thus we use the numeric value value. */ gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/, (unsigned int)pargs.r.ret_ulong, 0); break; case oListenBacklog: listen_backlog = pargs.r.ret_int; break; case oDebugQuickRandom: /* Only used by the first stage command line parser. */ break; case oWriteEnvFile: obsolete_option (configname, configlineno, "write-env-file"); break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ if (config_filename != configname) { xfree (config_filename); config_filename = configname; } configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); finalize_rereadable_options (); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } #ifdef ENABLE_NLS /* gpg-agent usually does not output any messages because it runs in the background. For log files it is acceptable to have messages always encoded in utf-8. We switch here to utf-8, so that commands like --help still give native messages. It is far easier to switch only once instead of for every message and it actually helps when more then one thread is active (avoids an extra copy step). */ bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); #endif if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised) { /* We have been called without any command and thus we merely check whether an agent is already running. We do this right here so that we don't clobber a logfile with this check but print the status directly to stderr. */ opt.debug = 0; set_debug (); check_for_running_agent (0); agent_exit (0); } if (is_supervised) ; else if (!opt.extra_socket) opt.extra_socket = 1; else if (socket_name_extra && (!strcmp (socket_name_extra, "none") || !strcmp (socket_name_extra, "/dev/null"))) { /* User requested not to create this socket. */ opt.extra_socket = 0; socket_name_extra = NULL; } if (is_supervised) ; else if (!opt.browser_socket) opt.browser_socket = 1; else if (socket_name_browser && (!strcmp (socket_name_browser, "none") || !strcmp (socket_name_browser, "/dev/null"))) { /* User requested not to create this socket. */ opt.browser_socket = 0; socket_name_browser = NULL; } set_debug (); if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } /* Try to create missing directories. */ create_directories (); if (debug_wait && pipe_server) { thread_init_once (); log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } if (gpgconf_list == 3) { /* We now use the standard socket always - return true for backward compatibility. */ agent_exit (0); } else if (gpgconf_list == 2) agent_exit (0); else if (gpgconf_list) { char *filename; char *filename_esc; /* List options and default values in the GPG Conf format. */ filename = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, GPG_AGENT_NAME, GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename); xfree (filename_esc); es_printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); es_printf ("default-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL ); es_printf ("default-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH ); es_printf ("max-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL ); es_printf ("max-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH ); es_printf ("enforce-passphrase-constraints:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("min-passphrase-len:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN ); es_printf ("min-passphrase-nonalpha:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_NONALPHA); es_printf ("check-passphrase-pattern:%lu:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("max-passphrase-days:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_PASSPHRASE_DAYS); es_printf ("enable-passphrase-history:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("ignore-cache-for-signing:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-external-cache:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-mark-trusted:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("disable-scdaemon:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE); es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, "md5"); #ifdef HAVE_W32_SYSTEM es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE); #endif es_printf ("no-allow-loopback-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("allow-emacs-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("pinentry-timeout:%lu:0:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("enable-extended-key-format:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); agent_exit (0); } /* Now start with logging to a file if this is desired. */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID)); current_logfile = xstrdup (logfile); } /* Make sure that we have a default ttyname. */ if (!default_ttyname && gnupg_ttyname (1)) default_ttyname = xstrdup (gnupg_ttyname (1)); if (!default_ttytype && getenv ("TERM")) default_ttytype = xstrdup (getenv ("TERM")); if (pipe_server) { /* This is the simple pipe based server */ ctrl_t ctrl; initialize_modules (); ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); agent_exit (1); } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); xfree (ctrl); agent_exit (1); } agent_init_default_ctrl (ctrl); start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD); agent_deinit_default_ctrl (ctrl); xfree (ctrl); } else if (is_supervised) { #ifndef HAVE_W32_SYSTEM gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh; initialize_modules (); /* when supervised and sending logs to stderr, the process supervisor should handle log entry metadata (pid, name, timestamp) */ if (!logfile) log_set_prefix (NULL, 0); log_info ("%s %s starting in supervised mode.\n", strusage(11), strusage(13) ); /* See below in "regular server mode" on why we remove certain * envvars. */ if (!opt.keep_display) gnupg_unsetenv ("DISPLAY"); gnupg_unsetenv ("INSIDE_EMACS"); /* Virtually create the sockets. Note that we use -1 here * because the whole thing works only on Unix. */ map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh); if (fd == -1) log_fatal ("no standard socket provided\n"); #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", fd, fd_extra, fd_browser, fd_ssh); handle_connections (fd, fd_extra, fd_browser, fd_ssh); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) ; /* NOTREACHED */ else { /* Regular server mode */ gnupg_fd_t fd; gnupg_fd_t fd_extra = GNUPG_INVALID_FD; gnupg_fd_t fd_browser = GNUPG_INVALID_FD; gnupg_fd_t fd_ssh = GNUPG_INVALID_FD; #ifndef HAVE_W32_SYSTEM pid_t pid; #endif /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default display when gpg-agent was started using --display or a client requested this using an OPTION command. Note, that we don't do this when running in reverse daemon mode (i.e. when exec the program given as arguments). */ #ifndef HAVE_W32_SYSTEM if (!opt.keep_display && !argc) gnupg_unsetenv ("DISPLAY"); #endif /* Remove the INSIDE_EMACS variable so that a pinentry does not always try to interact with Emacs. The variable is set when a client requested this using an OPTION command. */ gnupg_unsetenv ("INSIDE_EMACS"); /* Create the sockets. */ socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1); fd = create_server_socket (socket_name, 1, 0, &redir_socket_name, &socket_nonce); if (opt.extra_socket) { if (socket_name_extra) socket_name_extra = create_socket_name (socket_name_extra, 0); else socket_name_extra = create_socket_name /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1); opt.extra_socket = 2; /* Indicate that it has been malloced. */ fd_extra = create_server_socket (socket_name_extra, 0, 0, &redir_socket_name_extra, &socket_nonce_extra); } if (opt.browser_socket) { if (socket_name_browser) socket_name_browser = create_socket_name (socket_name_browser, 0); else socket_name_browser= create_socket_name /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1); opt.browser_socket = 2; /* Indicate that it has been malloced. */ fd_browser = create_server_socket (socket_name_browser, 0, 0, &redir_socket_name_browser, &socket_nonce_browser); } socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1); fd_ssh = create_server_socket (socket_name_ssh, 0, 1, &redir_socket_name_ssh, &socket_nonce_ssh); /* If we are going to exec a program in the parent, we record the PID, so that the child may check whether the program is still alive. */ if (argc) parent_pid = getpid (); fflush (NULL); #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; initialize_modules (); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* We are the parent */ char *infostr_ssh_sock, *infostr_ssh_valid; /* Close the socket FD. */ close (fd); /* The signal mask might not be correct right now and thus we restore it. That is not strictly necessary but some programs falsely assume a cleared signal mask. */ #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ /* Create the SSH info string if enabled. */ if (ssh_support) { if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", socket_name_ssh) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu", (unsigned long)getpid()) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } } *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ if (opt.extra_socket) *socket_name_extra = 0; if (opt.browser_socket) *socket_name_browser = 0; *socket_name_ssh = 0; if (argc) { /* Run the program given on the commandline. */ if (ssh_support && (putenv (infostr_ssh_sock) || putenv (infostr_ssh_valid))) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } /* Close all the file descriptors except the standard ones and those open at startup. We explicitly don't close 0,1,2 in case something went wrong collecting them at startup. */ close_all_fds (3, startup_fd_list); /* Run the command. */ execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { if (ssh_support) { *strchr (infostr_ssh_sock, '=') = ' '; es_printf ("setenv %s;\n", infostr_ssh_sock); } } else { if (ssh_support) { es_printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); } } if (ssh_support) { xfree (infostr_ssh_sock); xfree (infostr_ssh_valid); } exit (0); } /*NOTREACHED*/ } /* End parent */ /* This is the child */ initialize_modules (); /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; unsigned int oldflags; /* Close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) { if ( ! close (i) && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) { log_error ("failed to open '%s': %s\n", "/dev/null", strerror (errno)); cleanup (); exit (1); } } } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; /* Unless we are running with a program given on the command * line we can assume that the inotify things works and thus * we can avoid tye regular stat calls. */ if (!argc) reliable_homedir_inotify = 1; } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ if (gnupg_chdir (gnupg_daemon_rootdir ())) { log_error ("chdir to '%s' failed: %s\n", gnupg_daemon_rootdir (), strerror (errno)); exit (1); } log_info ("%s %s started\n", strusage(11), strusage(13) ); handle_connections (fd, fd_extra, fd_browser, fd_ssh); assuan_sock_close (fd); } return 0; } /* Exit entry point. This function should be called instead of a plain exit. */ void agent_exit (int rc) { /*FIXME: update_random_seed_file();*/ /* We run our cleanup handler because that may close cipher contexts stored in secure memory and thus this needs to be done before we explicitly terminate secure memory. */ cleanup (); #if 1 /* at this time a bit annoying */ if (opt.debug & DBG_MEMSTAT_VALUE) { gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); #endif gcry_control (GCRYCTL_TERM_SECMEM ); rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* This is our callback function for gcrypt progress messages. It is set once at startup and dispatches progress messages to the corresponding threads of the agent. */ static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); (void)data; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch && dispatch->cb) dispatch->cb (dispatch->ctrl, what, printchar, current, total); /* Libgcrypt < 1.8 does not know about nPth and thus when it reads * from /dev/random this will block the process. To mitigate this * problem we yield the thread when Libgcrypt tells us that it needs * more entropy. This way other threads have chance to run. */ #if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */ if (what && !strcmp (what, "need_entropy")) { #if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */ /* In older gpg-error versions gpgrt_yield is buggy for use with * nPth and thus we need to resort to a sleep call. */ npth_usleep (1000); /* 1ms */ #else gpgrt_yield (); #endif } #endif } /* If a progress dispatcher callback has been associated with the * current connection unregister it. */ static void unregister_progress_cb (void) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch) { dispatch->ctrl = NULL; dispatch->cb = NULL; } } /* Setup a progress callback CB for the current connection. Using a * CB of NULL disables the callback. */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl) { struct progress_dispatch_s *dispatch, *firstfree; npth_t mytid = npth_self (); firstfree = NULL; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) { if (dispatch->ctrl && dispatch->tid == mytid) break; if (!dispatch->ctrl && !firstfree) firstfree = dispatch; } if (!dispatch) /* None allocated: Reuse or allocate a new one. */ { if (firstfree) { dispatch = firstfree; } else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) { dispatch->next = progress_dispatch_list; progress_dispatch_list = dispatch; } else { log_error ("error allocating new progress dispatcher slot: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } dispatch->ctrl = ctrl; dispatch->tid = mytid; } dispatch->cb = cb; } /* Each thread has its own local variables conveyed by a control structure usually identified by an argument named CTRL. This function is called immediately after allocating the control structure. Its purpose is to setup the default values for that structure. Note that some values may have already been set. */ static void agent_init_default_ctrl (ctrl_t ctrl) { assert (ctrl->session_env); /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ session_env_setenv (ctrl->session_env, "DISPLAY", default_display); session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname); session_env_setenv (ctrl->session_env, "TERM", default_ttytype); session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority); session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL; if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) /**/ : NULL; ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET; } /* Release all resources allocated by default in the control structure. This is the counterpart to agent_init_default_ctrl. */ static void agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); if (ctrl->lc_messages) xfree (ctrl->lc_messages); } /* Because the ssh protocol does not send us information about the current TTY setting, we use this function to use those from startup or those explicitly set. This is also used for the restricted mode where we ignore requests to change the environment. */ gpg_error_t agent_copy_startup_env (ctrl_t ctrl) { - static const char *names[] = - {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL}; gpg_error_t err = 0; - int idx; - const char *value; + int iterator = 0; + const char *name, *value; - for (idx=0; !err && names[idx]; idx++) - if ((value = session_env_getenv (opt.startup_env, names[idx]))) - err = session_env_setenv (ctrl->session_env, names[idx], value); + while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) + { + if ((value = session_env_getenv (opt.startup_env, name))) + err = session_env_setenv (ctrl->session_env, name, value); + } if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) err = gpg_error_from_syserror (); if (!err && !ctrl->lc_messages && opt.startup_lc_messages) if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) err = gpg_error_from_syserror (); if (err) log_error ("error setting default session environment: %s\n", gpg_strerror (err)); return err; } /* Reread parts of the configuration. Note, that this function is obviously not thread-safe and should only be called from the PTH signal handler. Fixme: Due to the way the argument parsing works, we create a memory leak here for all string type arguments. There is currently no clean way to tell whether the memory for the argument has been allocated or points into the process' original arguments. Unless we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { ARGPARSE_ARGS pargs; FILE *fp; unsigned int configlineno = 0; int dummy; if (!config_filename) return; /* No config file. */ fp = fopen (config_filename, "r"); if (!fp) { log_info (_("option file '%s': %s\n"), config_filename, strerror(errno) ); return; } parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); finalize_rereadable_options (); set_debug (); } /* Return the file name of the socket we are using for native requests. */ const char * get_agent_socket_name (void) { const char *s = socket_name; return (s && *s)? s : NULL; } /* Return the file name of the socket we are using for SSH requests. */ const char * get_agent_ssh_socket_name (void) { const char *s = socket_name_ssh; return (s && *s)? s : NULL; } /* Return the number of active connections. */ int get_agent_active_connection_count (void) { return active_connections; } /* Under W32, this function returns the handle of the scdaemon notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) void * get_agent_scd_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; if (the_event == INVALID_HANDLE_VALUE) { HANDLE h, h2; SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; /* We need to use a manual reset event object due to the way our w32-pth wait function works: If we would use an automatic reset event we are not able to figure out which handle has been signaled because at the time we single out the signaled handles using WFSO the event has already been reset due to the WFMO. */ h = CreateEvent (&sa, TRUE, FALSE, NULL); if (!h) log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); else if (!DuplicateHandle (GetCurrentProcess(), h, GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { log_error ("setting syncronize for scd notify event failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } else { CloseHandle (h); the_event = h2; } } return the_event; } #endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/ /* Create a name for the socket in the home directory as using STANDARD_NAME. We also check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Returns: Pointer to an allocated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name, int with_homedir) { char *name; if (with_homedir) name = make_filename (gnupg_socketdir (), standard_name, NULL); else name = make_filename (standard_name, NULL); if (strchr (name, PATHSEP_C)) { log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); agent_exit (2); } return name; } /* Create a Unix domain socket with NAME. Returns the file descriptor or terminates the process in case of an error. Note that this function needs to be used for the regular socket first (indicated by PRIMARY) and only then for the extra and the ssh sockets. If the socket has been redirected the name of the real socket is stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a Cygwin compatible socket is created (Windows only). */ static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce) { struct sockaddr *addr; struct sockaddr_un *unaddr; socklen_t len; gnupg_fd_t fd; int rc; xfree (*r_redir_name); *r_redir_name = NULL; fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ agent_exit (2); } if (cygwin) assuan_sock_set_flag (fd, "cygwin", 1); unaddr = xmalloc (sizeof *unaddr); addr = (struct sockaddr*)unaddr; { int redirected; if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) { if (errno == ENAMETOOLONG) log_error (_("socket name '%s' is too long\n"), name); else log_error ("error preparing socket '%s': %s\n", name, gpg_strerror (gpg_error_from_syserror ())); *name = 0; /* Inhibit removal of the socket by cleanup(). */ xfree (unaddr); agent_exit (2); } if (redirected) { *r_redir_name = xstrdup (unaddr->sun_path); if (opt.verbose) log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); } } len = SUN_LEN (unaddr); rc = assuan_sock_bind (fd, addr, len); /* Our error code mapping on W32CE returns EEXIST thus we also test for this. */ if (rc == -1 && (errno == EADDRINUSE #ifdef HAVE_W32_SYSTEM || errno == EEXIST #endif )) { /* Check whether a gpg-agent is already running. We do this test only if this is the primary socket. For secondary sockets we assume that a test for gpg-agent has already been done and reuse the requested socket. Testing the ssh-socket is not possible because at this point, though we know the new Assuan socket, the Assuan server and thus the ssh-agent server is not yet operational; this would lead to a hang. */ if (primary && !check_for_running_agent (1)) { log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); log_set_file (NULL); log_error (_("a gpg-agent is already running - " "not starting a new one\n")); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); xfree (unaddr); agent_exit (2); } gnupg_remove (unaddr->sun_path); rc = assuan_sock_bind (fd, addr, len); } if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { /* We use gpg_strerror here because it allows us to get strings for some W32 socket error codes. */ log_error (_("error binding socket to '%s': %s\n"), unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); assuan_sock_close (fd); *name = 0; /* Inhibit removal of the socket by cleanup(). */ xfree (unaddr); agent_exit (2); } if (gnupg_chmod (unaddr->sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), unaddr->sun_path, strerror (errno)); if (listen (FD2INT(fd), listen_backlog ) == -1) { log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); xfree (unaddr); agent_exit (2); } if (opt.verbose) log_info (_("listening on socket '%s'\n"), unaddr->sun_path); xfree (unaddr); return fd; } /* Check that the directory for storing the private keys exists and create it if not. This function won't fail as it is only a convenience function and not strictly necessary. */ static void create_private_keys_directory (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); if (stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwx")) log_error (_("can't create directory '%s': %s\n"), fname, strerror (errno) ); else if (!opt.quiet) log_info (_("directory '%s' created\n"), fname); } if (gnupg_chmod (fname, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), fname, strerror (errno)); xfree (fname); } /* Create the directory only if the supplied directory name is the same as the default one. This way we avoid to create arbitrary directories when a non-default home directory is used. To cope with HOME, we compare only the suffix if we see that the default homedir does start with a tilde. We don't stop here in case of problems because other functions will throw an error anyway.*/ static void create_directories (void) { struct stat statbuf; const char *defhome = standard_homedir (); char *home; home = make_filename (gnupg_homedir (), NULL); if ( stat (home, &statbuf) ) { if (errno == ENOENT) { if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (home, defhome) ) #else (*defhome == '~' && (strlen (home) >= strlen (defhome+1) && !strcmp (home + strlen(home) - strlen (defhome+1), defhome+1))) || (*defhome != '~' && !strcmp (home, defhome) ) #endif ) { if (gnupg_mkdir (home, "-rwx")) log_error (_("can't create directory '%s': %s\n"), home, strerror (errno) ); else { if (!opt.quiet) log_info (_("directory '%s' created\n"), home); create_private_keys_directory (home); } } } else log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); } else if ( !S_ISDIR(statbuf.st_mode)) { log_error (_("can't use '%s' as home directory\n"), home); } else /* exists and is a directory. */ { create_private_keys_directory (home); } xfree (home); } /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { static time_t last_minute; struct stat statbuf; if (!last_minute) last_minute = time (NULL); /* Check whether the scdaemon has died and cleanup in this case. */ agent_scd_check_aliveness (); /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ #ifndef HAVE_W32_SYSTEM if (parent_pid != (pid_t)(-1)) { if (kill (parent_pid, 0)) { shutdown_pending = 2; log_info ("parent process died - shutting down\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } } #endif /*HAVE_W32_SYSTEM*/ /* Code to be run from time to time. */ #if CHECK_OWN_SOCKET_INTERVAL > 0 if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) { check_own_socket (); last_minute = time (NULL); } #endif /* Need to check for expired cache entries. */ agent_cache_housekeeping (); /* Check whether the homedir is still available. */ if (!shutdown_pending && (!have_homedir_inotify || !reliable_homedir_inotify) && stat (gnupg_homedir (), &statbuf) && errno == ENOENT) { shutdown_pending = 1; log_info ("homedir has been removed - shutting down\n"); } } /* A global function which allows us to call the reload stuff from other places too. This is only used when build for W32. */ void agent_sighup_action (void) { log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); agent_flush_cache (); reread_configuration (); agent_reload_trustlist (); /* We flush the module name cache so that after installing a "pinentry" binary that one can be used in case the "pinentry-basic" fallback was in use. */ gnupg_module_name_flush_some (); } /* A helper function to handle SIGUSR2. */ static void agent_sigusr2_action (void) { if (opt.verbose) log_info ("SIGUSR2 received - updating card event counter\n"); /* Nothing to check right now. We only increment a counter. */ bump_card_eventcounter (); } #ifndef HAVE_W32_SYSTEM /* The signal handler for this program. It is expected to be run in its own thread and not in the context of a signal handler. */ static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: agent_sighup_action (); break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); /* Fixme: We need to see how to integrate pth dumping into our logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ agent_query_dump_state (); agent_scd_dump_state (); break; case SIGUSR2: agent_sigusr2_action (); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %i open connections\n", active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); agent_exit (0); break; #endif default: log_info ("signal %d received - no action defined\n", signo); } } #endif /* Check the nonce on a new connection. This is a NOP unless we are using our Unix domain socket emulation under Windows. */ static int check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT(ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; } else return 0; } #ifdef HAVE_W32_SYSTEM /* The window message processing function for Putty. Warning: This code runs as a native Windows thread. Use of our own functions needs to be bracket with pth_leave/pth_enter. */ static LRESULT CALLBACK putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { int ret = 0; int w32rc; COPYDATASTRUCT *cds; const char *mapfile; HANDLE maphd; PSID mysid = NULL; PSID mapsid = NULL; void *data = NULL; PSECURITY_DESCRIPTOR psd = NULL; ctrl_t ctrl = NULL; if (msg != WM_COPYDATA) { return DefWindowProc (hwnd, msg, wparam, lparam); } cds = (COPYDATASTRUCT*)lparam; if (cds->dwData != PUTTY_IPC_MAGIC) return 0; /* Ignore data with the wrong magic. */ mapfile = cds->lpData; if (!cds->cbData || mapfile[cds->cbData - 1]) return 0; /* Ignore empty and non-properly terminated strings. */ if (DBG_IPC) { npth_protect (); log_debug ("ssh map file '%s'", mapfile); npth_unprotect (); } maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); if (DBG_IPC) { npth_protect (); log_debug ("ssh map handle %p\n", maphd); npth_unprotect (); } if (!maphd || maphd == INVALID_HANDLE_VALUE) return 0; npth_protect (); mysid = w32_get_user_sid (); if (!mysid) { log_error ("error getting my sid\n"); goto leave; } w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, &mapsid, NULL, NULL, NULL, &psd); if (w32rc) { log_error ("error getting sid of ssh map file: rc=%d", w32rc); goto leave; } if (DBG_IPC) { char *sidstr; if (!ConvertSidToStringSid (mysid, &sidstr)) sidstr = NULL; log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); if (!ConvertSidToStringSid (mapsid, &sidstr)) sidstr = NULL; log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); } if (!EqualSid (mysid, mapsid)) { log_error ("ssh map file has a non-matching sid\n"); goto leave; } data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (DBG_IPC) log_debug ("ssh IPC buffer at %p\n", data); if (!data) goto leave; /* log_printhex ("request:", data, 20); */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); goto leave; } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); goto leave; } agent_init_default_ctrl (ctrl); if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) ret = 1; /* Valid ssh message has been constructed. */ agent_deinit_default_ctrl (ctrl); /* log_printhex (" reply:", data, 20); */ leave: xfree (ctrl); if (data) UnmapViewOfFile (data); xfree (mapsid); if (psd) LocalFree (psd); xfree (mysid); CloseHandle (maphd); npth_unprotect (); return ret; } #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* The thread handling Putty's IPC requests. */ static void * putty_message_thread (void *arg) { WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, NULL, NULL, NULL, NULL, NULL, "Pageant"}; HWND hwnd; MSG msg; (void)arg; if (opt.verbose) log_info ("putty message loop thread started\n"); /* The message loop runs as thread independent from our nPth system. This also means that we need to make sure that we switch back to our system before calling any no-windows function. */ npth_unprotect (); /* First create a window to make sure that a message queue exists for this thread. */ if (!RegisterClass (&wndwclass)) { npth_protect (); log_error ("error registering Pageant window class"); return NULL; } hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, 0, 0, 0, 0, HWND_MESSAGE, /* hWndParent */ NULL, /* hWndMenu */ NULL, /* hInstance */ NULL); /* lpParm */ if (!hwnd) { npth_protect (); log_error ("error creating Pageant window"); return NULL; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* Back to nPth. */ npth_protect (); if (opt.verbose) log_info ("putty message loop thread stopped\n"); return NULL; } #endif /*HAVE_W32_SYSTEM*/ static void * do_start_connection_thread (ctrl_t ctrl) { active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* This is the standard connection thread's main function. */ static void * start_connection_thread_std (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } return do_start_connection_thread (ctrl); } /* This is the extra socket connection thread's main function. */ static void * start_connection_thread_extra (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_extra)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 1; return do_start_connection_thread (ctrl); } /* This is the browser socket connection thread's main function. */ static void * start_connection_thread_browser (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_browser)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 2; return do_start_connection_thread (ctrl); } /* This is the ssh connection thread's main function. */ static void * start_connection_thread_ssh (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_ssh)) return NULL; active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. */ static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh) { gpg_error_t err; npth_attr_t tattr; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int ret; gnupg_fd_t fd; int nfd; int saved_errno; struct timespec abstime; struct timespec curtime; struct timespec timeout; #ifdef HAVE_W32_SYSTEM HANDLE events[2]; unsigned int events_set; #endif int sock_inotify_fd = -1; int home_inotify_fd = -1; struct { const char *name; void *(*func) (void *arg); gnupg_fd_t l_fd; } listentbl[] = { { "std", start_connection_thread_std }, { "extra", start_connection_thread_extra }, { "browser", start_connection_thread_browser }, { "ssh", start_connection_thread_ssh } }; ret = npth_attr_init(&tattr); if (ret) log_fatal ("error allocating thread attributes: %s\n", strerror (ret)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); #ifndef HAVE_W32_SYSTEM npth_sigev_init (); npth_sigev_add (SIGHUP); npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); npth_sigev_add (SIGTERM); npth_sigev_fini (); #else # ifdef HAVE_W32CE_SYSTEM /* Use a dummy event. */ sigs = 0; ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); # else events[0] = get_agent_scd_notify_event (); events[1] = INVALID_HANDLE_VALUE; # endif #endif if (disable_check_own_socket) sock_inotify_fd = -1; else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling daemon termination by socket removal: %s\n", gpg_strerror (err)); } if (disable_check_own_socket) home_inotify_fd = -1; else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, gnupg_homedir ()))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling daemon termination by homedir removal: %s\n", gpg_strerror (err)); } else have_homedir_inotify = 1; /* On Windows we need to fire up a separate thread to listen for requests from Putty (an SSH client), so we can replace Putty's Pageant (its ssh-agent implementation). */ #ifdef HAVE_W32_SYSTEM if (putty_support) { npth_t thread; ret = npth_create (&thread, &tattr, putty_message_thread, NULL); if (ret) { log_error ("error spawning putty message loop: %s\n", strerror (ret)); } } #endif /*HAVE_W32_SYSTEM*/ /* Set a flag to tell call-scd.c that it may enable event notifications. */ opt.sigusr2_enabled = 1; FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); nfd = FD2INT (listen_fd); if (listen_fd_extra != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_extra), &fdset); if (FD2INT (listen_fd_extra) > nfd) nfd = FD2INT (listen_fd_extra); } if (listen_fd_browser != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_browser), &fdset); if (FD2INT (listen_fd_browser) > nfd) nfd = FD2INT (listen_fd_browser); } if (listen_fd_ssh != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_ssh), &fdset); if (FD2INT (listen_fd_ssh) > nfd) nfd = FD2INT (listen_fd_ssh); } if (sock_inotify_fd != -1) { FD_SET (sock_inotify_fd, &fdset); if (sock_inotify_fd > nfd) nfd = sock_inotify_fd; } if (home_inotify_fd != -1) { FD_SET (home_inotify_fd, &fdset); if (home_inotify_fd > nfd) nfd = home_inotify_fd; } listentbl[0].l_fd = listen_fd; listentbl[1].l_fd = listen_fd_extra; listentbl[2].l_fd = listen_fd_browser; listentbl[3].l_fd = listen_fd_ssh; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; for (;;) { /* Shutdown test. */ if (shutdown_pending) { if (active_connections == 0) break; /* ready */ /* Do not accept new connections but keep on running the * loop to cope with the timer events. * * Note that we do not close the listening socket because a * client trying to connect to that socket would instead * restart a new dirmngr instance - which is unlikely the * intention of a shutdown. */ FD_ZERO (&fdset); nfd = -1; if (sock_inotify_fd != -1) { FD_SET (sock_inotify_fd, &fdset); nfd = sock_inotify_fd; } if (home_inotify_fd != -1) { FD_SET (home_inotify_fd, &fdset); if (home_inotify_fd > nfd) nfd = home_inotify_fd; } } /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); #ifndef HAVE_W32_SYSTEM ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask ()); saved_errno = errno; { int signo; while (npth_sigev_get_pending (&signo)) handle_signal (signo); } #else ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, events, &events_set); saved_errno = errno; /* This is valid even if npth_eselect returns an error. */ if (events_set & 1) agent_sigusr2_action (); #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); npth_sleep (1); continue; } if (ret <= 0) /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; /* The inotify fds are set even when a shutdown is pending (see * above). So we must handle them in any case. To avoid that * they trigger a second time we close them immediately. */ if (sock_inotify_fd != -1 && FD_ISSET (sock_inotify_fd, &read_fdset) && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) { shutdown_pending = 1; close (sock_inotify_fd); sock_inotify_fd = -1; log_info ("socket file has been removed - shutting down\n"); } if (home_inotify_fd != -1 && FD_ISSET (home_inotify_fd, &read_fdset)) { shutdown_pending = 1; close (home_inotify_fd); home_inotify_fd = -1; log_info ("homedir has been removed - shutting down\n"); } if (!shutdown_pending) { int idx; ctrl_t ctrl; npth_t thread; for (idx=0; idx < DIM(listentbl); idx++) { if (listentbl[idx].l_fd == GNUPG_INVALID_FD) continue; if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) continue; plen = sizeof paddr; fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), (struct sockaddr *)&paddr, &plen)); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed for %s: %s\n", listentbl[idx].name, strerror (errno)); } else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) { log_error ("error allocating connection data for %s: %s\n", listentbl[idx].name, strerror (errno) ); assuan_sock_close (fd); } else if ( !(ctrl->session_env = session_env_new ())) { log_error ("error allocating session env block for %s: %s\n", listentbl[idx].name, strerror (errno) ); xfree (ctrl); assuan_sock_close (fd); } else { ctrl->thread_startup.fd = fd; ret = npth_create (&thread, &tattr, listentbl[idx].func, ctrl); if (ret) { log_error ("error spawning connection handler for %s:" " %s\n", listentbl[idx].name, strerror (ret)); assuan_sock_close (fd); xfree (ctrl); } } } } } if (sock_inotify_fd != -1) close (sock_inotify_fd); if (home_inotify_fd != -1) close (home_inotify_fd); cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); npth_attr_destroy (&tattr); } /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) { membuf_t *mb = opaque; put_membuf (mb, buffer, length); return 0; } /* The thread running the actual check. We need to run this in a separate thread so that check_own_thread can be called from the timer tick. */ static void * check_own_socket_thread (void *arg) { int rc; char *sockname = arg; assuan_context_t ctx = NULL; membuf_t mb; char *buffer; check_own_socket_running++; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); goto leave; } assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); if (rc) { log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); goto leave; } init_membuf (&mb, 100); rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, NULL, NULL, NULL, NULL); put_membuf (&mb, "", 1); buffer = get_membuf (&mb, NULL); if (rc || !buffer) { log_error ("sending command \"%s\" to my own socket failed: %s\n", "GETINFO pid", gpg_strerror (rc)); rc = 1; } else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) { log_error ("socket is now serviced by another server\n"); rc = 1; } else if (opt.verbose > 1) log_error ("socket is still served by this server\n"); xfree (buffer); leave: xfree (sockname); if (ctx) assuan_release (ctx); if (rc) { /* We may not remove the socket as it is now in use by another server. */ inhibit_socket_removal = 1; shutdown_pending = 2; log_info ("this process is useless - shutting down\n"); } check_own_socket_running--; return NULL; } /* Check whether we are still listening on our own socket. In case another gpg-agent process started after us has taken ownership of our socket, we would linger around without any real task. Thus we better check once in a while whether we are really needed. */ static void check_own_socket (void) { char *sockname; npth_t thread; npth_attr_t tattr; int err; if (disable_check_own_socket) return; if (check_own_socket_running || shutdown_pending) return; /* Still running or already shutting down. */ sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return; /* Out of memory. */ err = npth_attr_init (&tattr); if (err) return; npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); if (err) log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. Returns 0 if the agent is running. */ static int check_for_running_agent (int silent) { gpg_error_t err; char *sockname; assuan_context_t ctx = NULL; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return gpg_error_from_syserror (); err = assuan_new (&ctx); if (!err) err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); xfree (sockname); if (err) { if (!silent) log_error (_("no gpg-agent running in this session\n")); if (ctx) assuan_release (ctx); return -1; } if (!opt.quiet && !silent) log_info ("gpg-agent running and available\n"); assuan_release (ctx); return 0; } diff --git a/common/Makefile.am b/common/Makefile.am index 94318dae4..d288fa36b 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -1,229 +1,229 @@ # Makefile for common gnupg modules # Copyright (C) 2001, 2003, 2007, 2010 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GnuPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . ## Process this file with automake to produce Makefile.in EXTRA_DIST = mkstrtable.awk exaudit.awk exstatus.awk ChangeLog-2011 \ audit-events.h status-codes.h ChangeLog.jnlib \ ChangeLog-2011.include w32info-rc.h.in gnupg.ico \ all-tests.scm noinst_LIBRARIES = libcommon.a libcommonpth.a libgpgrl.a if !HAVE_W32CE_SYSTEM noinst_LIBRARIES += libsimple-pwquery.a endif noinst_PROGRAMS = $(module_tests) $(module_maint_tests) TESTS = $(module_tests) BUILT_SOURCES = audit-events.h status-codes.h MAINTAINERCLEANFILES = audit-events.h status-codes.h AM_CPPFLAGS = AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) include $(top_srcdir)/am/cmacros.am common_sources = \ common-defs.h \ util.h utilproto.h fwddecl.h i18n.c i18n.h \ types.h host2net.h dynload.h w32help.h \ mapstrings.c stringhelp.c stringhelp.h \ strlist.c strlist.h \ utf8conv.c utf8conv.h \ argparse.c argparse.h \ - logging.c logging.h \ + logging.h \ dotlock.c dotlock.h \ mischelp.c mischelp.h \ status.c status.h\ shareddefs.h \ openpgpdefs.h \ gc-opt-flags.h \ keyserver.h \ sexp-parse.h \ tlv.c tlv.h \ 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 \ convert.c \ percent.c \ mbox-util.c mbox-util.h \ miscellaneous.c \ xasprintf.c \ xreadline.c \ membuf.c membuf.h \ ccparray.c ccparray.h \ iobuf.c iobuf.h \ ttyio.c ttyio.h \ asshelp.c asshelp2.c asshelp.h \ exechelp.h \ signal.c \ audit.c audit.h \ localename.c \ session-env.c session-env.h \ userids.c userids.h \ openpgp-oid.c \ ssh-utils.c ssh-utils.h \ agent-opt.c \ helpfile.c \ mkdir_p.c mkdir_p.h \ strlist.c strlist.h \ exectool.c exectool.h \ server-help.c server-help.h \ name-value.c name-value.h \ recsel.c recsel.h \ ksba-io-support.c ksba-io-support.h \ compliance.c compliance.h \ pkscreening.c pkscreening.h if HAVE_W32_SYSTEM common_sources += w32-reg.c endif # To make the code easier to read we have split home some code into # separate source files. if HAVE_W32_SYSTEM if HAVE_W32CE_SYSTEM common_sources += exechelp-w32ce.c else common_sources += exechelp-w32.c endif else common_sources += exechelp-posix.c endif # Sources only useful without NPTH. without_npth_sources = \ get-passphrase.c get-passphrase.h # Sources only useful with NPTH. with_npth_sources = \ call-gpg.c call-gpg.h libcommon_a_SOURCES = $(common_sources) $(without_npth_sources) libcommon_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) -DWITHOUT_NPTH=1 libcommonpth_a_SOURCES = $(common_sources) $(with_npth_sources) libcommonpth_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) if !HAVE_W32CE_SYSTEM libsimple_pwquery_a_SOURCES = \ simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h libsimple_pwquery_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) endif libgpgrl_a_SOURCES = \ gpgrlhelp.c if MAINTAINER_MODE # Note: Due to the dependency on Makefile, the file will always be # rebuilt, so we allow this only in maintainer mode. # Create the audit-events.h include file from audit.h # Note: We create the target file in the source directory because it # is a distributed built source. If we would not do that we may end # up with two files and then it is not clear which version of the # files will be picked up. audit-events.h: Makefile.am mkstrtable.awk exaudit.awk audit.h $(AWK) -f $(srcdir)/exaudit.awk $(srcdir)/audit.h \ | $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \ -v namespace=eventstr_ > $(srcdir)/audit-events.h # Create the status-codes.h include file from status.h status-codes.h: Makefile.am mkstrtable.awk exstatus.awk status.h $(AWK) -f $(srcdir)/exstatus.awk $(srcdir)/status.h \ | $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \ -v namespace=statusstr_ > $(srcdir)/status-codes.h endif # # Module tests # 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 if !HAVE_W32CE_SYSTEM module_tests += t-exechelp t-exectool endif if HAVE_W32_SYSTEM module_tests += t-w32-reg endif if MAINTAINER_MODE module_maint_tests = t-helpfile t-b64 else module_maint_tests = endif t_extra_src = t-support.h t_common_cflags = $(KSBA_CFLAGS) $(LIBGCRYPT_CFLAGS) \ $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) t_common_ldadd = libcommon.a \ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) # Common tests t_stringhelp_SOURCES = t-stringhelp.c $(t_extra_src) t_stringhelp_LDADD = $(t_common_ldadd) t_timestuff_SOURCES = t-timestuff.c $(t_extra_src) t_timestuff_LDADD = $(t_common_ldadd) t_convert_LDADD = $(t_common_ldadd) t_percent_LDADD = $(t_common_ldadd) 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) t_openpgp_oid_LDADD = $(t_common_ldadd) t_ssh_utils_LDADD = $(t_common_ldadd) t_mapstrings_LDADD = $(t_common_ldadd) t_zb32_SOURCES = t-zb32.c $(t_extra_src) t_zb32_LDADD = $(t_common_ldadd) t_mbox_util_LDADD = $(t_common_ldadd) t_iobuf_LDADD = $(t_common_ldadd) t_strlist_LDADD = $(t_common_ldadd) t_name_value_LDADD = $(t_common_ldadd) t_ccparray_LDADD = $(t_common_ldadd) t_recsel_LDADD = $(t_common_ldadd) # System specific test if HAVE_W32_SYSTEM t_w32_reg_SOURCES = t-w32-reg.c $(t_extra_src) t_w32_reg_LDADD = $(t_common_ldadd) endif # All programs should depend on the created libs. $(PROGRAMS) : libcommon.a libcommonpth.a diff --git a/common/init.c b/common/init.c index 86b71e5ee..f62c5cd58 100644 --- a/common/init.c +++ b/common/init.c @@ -1,289 +1,293 @@ /* init.c - Various initializations * Copyright (C) 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #ifdef HAVE_W32CE_SYSTEM # include /* For _assuan_w32ce_finish_pipe. */ #endif #include #include "util.h" #include "i18n.h" /* This object is used to register memory cleanup functions. Technically they are not needed but they can avoid frequent questions about un-released memory. Note that we use the system malloc and not any wrappers. */ struct mem_cleanup_item_s; typedef struct mem_cleanup_item_s *mem_cleanup_item_t; struct mem_cleanup_item_s { mem_cleanup_item_t next; void (*func) (void); }; static mem_cleanup_item_t mem_cleanup_list; /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Note that we need to initialize it because otherwise some linkers (OS X at least) won't find the symbol when linking the t-*.c files. */ gpg_err_source_t default_errsource = 0; #ifdef HAVE_W32CE_SYSTEM static void parse_std_file_handles (int *argcp, char ***argvp); static void sleep_on_exit (void) { /* The sshd on CE swallows some of the command output. Sleeping a while usually helps. */ Sleep (400); } #endif /*HAVE_W32CE_SYSTEM*/ static void run_mem_cleanup (void) { mem_cleanup_item_t next; while (mem_cleanup_list) { next = mem_cleanup_list->next; mem_cleanup_list->func (); free (mem_cleanup_list); mem_cleanup_list = next; } } void register_mem_cleanup_func (void (*func)(void)) { mem_cleanup_item_t item; for (item = mem_cleanup_list; item; item = item->next) if (item->func == func) return; /* Function has already been registered. */ item = malloc (sizeof *item); if (item) { item->func = func; item->next = mem_cleanup_list; mem_cleanup_list = item; } } /* If STRING is not NULL write string to es_stdout or es_stderr. MODE must be 1 or 2. If STRING is NULL flush the respective stream. */ static int writestring_via_estream (int mode, const char *string) { if (mode == 1 || mode == 2) { if (string) return es_fputs (string, mode == 1? es_stdout : es_stderr); else return es_fflush (mode == 1? es_stdout : es_stderr); } else return -1; } /* This function should be the first called after main. */ void early_system_init (void) { } /* This function is to be used early at program startup to make sure that some subsystems are initialized. This is in particular important for W32 to initialize the sockets so that our socket emulation code used directly as well as in libassuan may be used. It should best be called before any I/O is done so that setup required for logging is ready. ARGCP and ARGVP are the addresses of the parameters given to main. This function may modify them. This function should be called only via the macro init_common_subsystems. CAUTION: This might be called while running suid(root). */ void _init_common_subsystems (gpg_err_source_t errsource, int *argcp, char ***argvp) { /* Store the error source in a global variable. */ default_errsource = errsource; atexit (run_mem_cleanup); /* Try to auto set the character set. */ set_native_charset (NULL); #ifdef HAVE_W32_SYSTEM /* For W32 we need to initialize the socket layer. This is because we use recv and send in libassuan as well as at some other places. */ { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif #ifdef HAVE_W32CE_SYSTEM /* Register the sleep exit function before the estream init so that the sleep will be called after the estream registered atexit function which flushes the left open estream streams and in particular es_stdout. */ atexit (sleep_on_exit); #endif if (!gcry_check_version (NEED_LIBGCRYPT_VERSION)) { log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt", NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL)); } /* Initialize the Estream library. */ gpgrt_init (); gpgrt_set_alloc_func (gcry_realloc); /* Special hack for Windows CE: We extract some options from arg to setup the standard handles. */ #ifdef HAVE_W32CE_SYSTEM parse_std_file_handles (argcp, argvp); #else (void)argcp; (void)argvp; #endif /* Access the standard estreams as early as possible. If we don't do this the original stdio streams may have been closed when _es_get_std_stream is first use and in turn it would connect to the bit bucket. */ { int i; for (i=0; i < 3; i++) (void)_gpgrt_get_std_stream (i); } /* --version et al shall use estream as well. */ - argparse_register_outfnc (writestring_via_estream); + argparse_register_outfnc (writestring_via_estream); /* legacy. */ + gpgrt_set_usage_outfnc (writestring_via_estream); + + /* Register our string mapper with gpgrt. */ + gpgrt_set_fixed_string_mapper (map_static_macro_string); /* Logging shall use the standard socket directory as fallback. */ log_set_socket_dir_cb (gnupg_socketdir); } /* WindowsCE uses a very strange way of handling the standard streams. There is a function SetStdioPath to associate a standard stream with a file or a device but what we really want is to use pipes as standard streams. Despite that we implement pipes using a device, we would have some limitations on the number of open pipes due to the 3 character limit of device file name. Thus we don't take this path. Another option would be to install a file system driver with support for pipes; this would allow us to get rid of the device name length limitation. However, with GnuPG we can get away be redefining the standard streams and passing the handles to be used on the command line. This has also the advantage that it makes creating a process much easier and does not require the SetStdioPath set and restore game. The caller needs to pass the rendezvous ids using up to three options: -&S0= -&S1= -&S2= They are all optional but they must be the first arguments on the command line. Parsing stops as soon as an invalid option is found. These rendezvous ids are then used to finish the pipe creation.*/ #ifdef HAVE_W32CE_SYSTEM static void parse_std_file_handles (int *argcp, char ***argvp) { int argc = *argcp; char **argv = *argvp; const char *s; assuan_fd_t fd; int i; int fixup = 0; if (!argc) return; for (argc--, argv++; argc; argc--, argv++) { s = *argv; if (*s == '-' && s[1] == '&' && s[2] == 'S' && (s[3] == '0' || s[3] == '1' || s[3] == '2') && s[4] == '=' && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null"))) { if (s[5] == 'n') fd = ASSUAN_INVALID_FD; else fd = _assuan_w32ce_finish_pipe (atoi (s+5), s[3] != '0'); _es_set_std_fd (s[3] - '0', (int)fd); fixup++; } else break; } if (fixup) { argc = *argcp; argc -= fixup; *argcp = argc; argv = *argvp; for (i=1; i < argc; i++) argv[i] = argv[i + fixup]; for (; i < argc + fixup; i++) argv[i] = NULL; } } #endif /*HAVE_W32CE_SYSTEM*/ diff --git a/common/logging.c b/common/logging.c deleted file mode 100644 index 88860e715..000000000 --- a/common/logging.c +++ /dev/null @@ -1,1121 +0,0 @@ -/* logging.c - Useful logging functions - * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, - * 2009, 2010 Free Software Foundation, Inc. - * - * This file is part of GnuPG. - * - * GnuPG is free software; you can redistribute and/or modify this - * part of GnuPG under the terms of either - * - * - the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at - * your option) any later version. - * - * or - * - * - the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * or both in parallel, as here. - * - * GnuPG is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copies of the GNU General Public License - * and the GNU Lesser General Public License along with this program; - * if not, see . - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_W32_SYSTEM -# ifdef HAVE_WINSOCK2_H -# include -# endif -# include -#else /*!HAVE_W32_SYSTEM*/ -# include -# include -# include -# include -#endif /*!HAVE_W32_SYSTEM*/ -#include -#include -#include -/* #include */ - -#define GNUPG_COMMON_NEED_AFLOCAL 1 -#include "util.h" -#include "i18n.h" -#include "common-defs.h" -#include "logging.h" -#include "sysutils.h" - -#if defined(GPGRT_ENABLE_LOG_MACROS) && defined(log_debug_string) - /* Nothing to do; the libgpgrt functions are used. */ -#else /* Use our own logging functions. */ - -#ifdef HAVE_W32_SYSTEM -# ifndef S_IRWXG -# define S_IRGRP S_IRUSR -# define S_IWGRP S_IWUSR -# endif -# ifndef S_IRWXO -# define S_IROTH S_IRUSR -# define S_IWOTH S_IWUSR -# endif -#endif - - -#ifdef HAVE_W32CE_SYSTEM -# define isatty(a) (0) -#endif - -#undef WITH_IPV6 -#if defined (AF_INET6) && defined(PF_INET) \ - && defined (INET6_ADDRSTRLEN) && defined(HAVE_INET_PTON) -# define WITH_IPV6 1 -#endif - -#ifndef EAFNOSUPPORT -# define EAFNOSUPPORT EINVAL -#endif -#ifndef INADDR_NONE /* Slowaris is missing that. */ -#define INADDR_NONE ((unsigned long)(-1)) -#endif /*INADDR_NONE*/ - -#ifdef HAVE_W32_SYSTEM -#define sock_close(a) closesocket(a) -#else -#define sock_close(a) close(a) -#endif - - -static estream_t logstream; -static int log_socket = -1; -static char prefix_buffer[80]; -static int with_time; -static int with_prefix; -static int with_pid; -#ifdef HAVE_W32_SYSTEM -static int no_registry; -#endif -static int (*get_pid_suffix_cb)(unsigned long *r_value); -static const char * (*socket_dir_cb)(void); -static int running_detached; -static int force_prefixes; - -static int missing_lf; -static int errorcount; - - -int -log_get_errorcount (int clear) -{ - int n = errorcount; - if( clear ) - errorcount = 0; - return n; -} - -void -log_inc_errorcount (void) -{ - errorcount++; -} - - -/* The following 3 functions are used by es_fopencookie to write logs - to a socket. */ -struct fun_cookie_s -{ - int fd; - int quiet; - int want_socket; - int is_socket; -#ifdef HAVE_W32CE_SYSTEM - int use_writefile; -#endif - char name[1]; -}; - - -/* Write NBYTES of BUFFER to file descriptor FD. */ -static int -writen (int fd, const void *buffer, size_t nbytes, int is_socket) -{ - const char *buf = buffer; - size_t nleft = nbytes; - int nwritten; -#ifndef HAVE_W32_SYSTEM - (void)is_socket; /* Not required. */ -#endif - - while (nleft > 0) - { -#ifdef HAVE_W32_SYSTEM - if (is_socket) - nwritten = send (fd, buf, nleft, 0); - else -#endif - nwritten = write (fd, buf, nleft); - - if (nwritten < 0 && errno == EINTR) - continue; - if (nwritten < 0) - return -1; - nleft -= nwritten; - buf = buf + nwritten; - } - - return 0; -} - - -/* Returns true if STR represents a valid port number in decimal - notation and no garbage is following. */ -static int -parse_portno (const char *str, unsigned short *r_port) -{ - unsigned int value; - - for (value=0; *str && (*str >= '0' && *str <= '9'); str++) - { - value = value * 10 + (*str - '0'); - if (value > 65535) - return 0; - } - if (*str || !value) - return 0; - - *r_port = value; - return 1; -} - - -static gpgrt_ssize_t -fun_writer (void *cookie_arg, const void *buffer, size_t size) -{ - struct fun_cookie_s *cookie = cookie_arg; - - /* FIXME: Use only estream with a callback for socket writing. This - avoids the ugly mix of fd and estream code. */ - - /* Note that we always try to reconnect to the socket but print - error messages only the first time an error occurred. If - RUNNING_DETACHED is set we don't fall back to stderr and even do - not print any error messages. This is needed because detached - processes often close stderr and by writing to file descriptor 2 - we might send the log message to a file not intended for logging - (e.g. a pipe or network connection). */ - if (cookie->want_socket && cookie->fd == -1) - { -#ifdef WITH_IPV6 - struct sockaddr_in6 srvr_addr_in6; -#endif - struct sockaddr_in srvr_addr_in; -#ifndef HAVE_W32_SYSTEM - struct sockaddr_un srvr_addr_un; -#endif - const char *name_for_err = ""; - size_t addrlen; - struct sockaddr *srvr_addr = NULL; - unsigned short port = 0; - int af = AF_LOCAL; - int pf = PF_LOCAL; - const char *name = cookie->name; - - /* Not yet open or meanwhile closed due to an error. */ - cookie->is_socket = 0; - - /* Check whether this is a TCP socket or a local socket. */ - if (!strncmp (name, "tcp://", 6) && name[6]) - { - name += 6; - af = AF_INET; - pf = PF_INET; - } -#ifndef HAVE_W32_SYSTEM - else if (!strncmp (name, "socket://", 9)) - name += 9; -#endif - - if (af == AF_LOCAL) - { - addrlen = 0; -#ifndef HAVE_W32_SYSTEM - memset (&srvr_addr, 0, sizeof srvr_addr); - srvr_addr_un.sun_family = af; - if (!*name && (name = socket_dir_cb ()) && *name) - { - if (strlen (name) + 7 < sizeof (srvr_addr_un.sun_path)-1) - { - strncpy (srvr_addr_un.sun_path, - name, sizeof (srvr_addr_un.sun_path)-1); - strcat (srvr_addr_un.sun_path, "/S.log"); - srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path)-1] = 0; - srvr_addr = (struct sockaddr *)&srvr_addr_un; - addrlen = SUN_LEN (&srvr_addr_un); - name_for_err = srvr_addr_un.sun_path; - } - } - else - { - if (*name && strlen (name) < sizeof (srvr_addr_un.sun_path)-1) - { - strncpy (srvr_addr_un.sun_path, - name, sizeof (srvr_addr_un.sun_path)-1); - srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path)-1] = 0; - srvr_addr = (struct sockaddr *)&srvr_addr_un; - addrlen = SUN_LEN (&srvr_addr_un); - } - } -#endif /*!HAVE_W32SYSTEM*/ - } - else - { - char *addrstr, *p; -#ifdef HAVE_INET_PTON - void *addrbuf = NULL; -#endif /*HAVE_INET_PTON*/ - - addrstr = xtrymalloc (strlen (name) + 1); - if (!addrstr) - addrlen = 0; /* This indicates an error. */ - else if (*name == '[') - { - /* Check for IPv6 literal address. */ - strcpy (addrstr, name+1); - p = strchr (addrstr, ']'); - if (!p || p[1] != ':' || !parse_portno (p+2, &port)) - { - gpg_err_set_errno (EINVAL); - addrlen = 0; - } - else - { - *p = 0; -#ifdef WITH_IPV6 - af = AF_INET6; - pf = PF_INET6; - memset (&srvr_addr_in6, 0, sizeof srvr_addr_in6); - srvr_addr_in6.sin6_family = af; - srvr_addr_in6.sin6_port = htons (port); -#ifdef HAVE_INET_PTON - addrbuf = &srvr_addr_in6.sin6_addr; -#endif /*HAVE_INET_PTON*/ - srvr_addr = (struct sockaddr *)&srvr_addr_in6; - addrlen = sizeof srvr_addr_in6; -#else - gpg_err_set_errno (EAFNOSUPPORT); - addrlen = 0; -#endif - } - } - else - { - /* Check for IPv4 literal address. */ - strcpy (addrstr, name); - p = strchr (addrstr, ':'); - if (!p || !parse_portno (p+1, &port)) - { - gpg_err_set_errno (EINVAL); - addrlen = 0; - } - else - { - *p = 0; - memset (&srvr_addr_in, 0, sizeof srvr_addr_in); - srvr_addr_in.sin_family = af; - srvr_addr_in.sin_port = htons (port); -#ifdef HAVE_INET_PTON - addrbuf = &srvr_addr_in.sin_addr; -#endif /*HAVE_INET_PTON*/ - srvr_addr = (struct sockaddr *)&srvr_addr_in; - addrlen = sizeof srvr_addr_in; - } - } - - if (addrlen) - { -#ifdef HAVE_INET_PTON - if (inet_pton (af, addrstr, addrbuf) != 1) - addrlen = 0; -#else /*!HAVE_INET_PTON*/ - /* We need to use the old function. If we are here v6 - support isn't enabled anyway and thus we can do fine - without. Note that Windows has a compatible inet_pton - function named inetPton, but only since Vista. */ - srvr_addr_in.sin_addr.s_addr = inet_addr (addrstr); - if (srvr_addr_in.sin_addr.s_addr == INADDR_NONE) - addrlen = 0; -#endif /*!HAVE_INET_PTON*/ - } - - xfree (addrstr); - } - - cookie->fd = addrlen? socket (pf, SOCK_STREAM, 0) : -1; - if (cookie->fd == -1) - { - if (!cookie->quiet && !running_detached - && isatty (es_fileno (es_stderr))) - es_fprintf (es_stderr, "failed to create socket for logging: %s\n", - strerror(errno)); - } - else - { - if (connect (cookie->fd, srvr_addr, addrlen) == -1) - { - if (!cookie->quiet && !running_detached - && isatty (es_fileno (es_stderr))) - es_fprintf (es_stderr, "can't connect to '%s%s': %s\n", - cookie->name, name_for_err, strerror(errno)); - sock_close (cookie->fd); - cookie->fd = -1; - } - } - - if (cookie->fd == -1) - { - if (!running_detached) - { - /* Due to all the problems with apps not running - detached but being called with stderr closed or used - for a different purposes, it does not make sense to - switch to stderr. We therefore disable it. */ - if (!cookie->quiet) - { - /* fputs ("switching logging to stderr\n", stderr);*/ - cookie->quiet = 1; - } - cookie->fd = -1; /*fileno (stderr);*/ - } - } - else /* Connection has been established. */ - { - cookie->quiet = 0; - cookie->is_socket = 1; - } - } - - log_socket = cookie->fd; - if (cookie->fd != -1) - { -#ifdef HAVE_W32CE_SYSTEM - if (cookie->use_writefile) - { - DWORD nwritten; - - WriteFile ((HANDLE)cookie->fd, buffer, size, &nwritten, NULL); - return (gpgrt_ssize_t)size; /* Okay. */ - } -#endif - if (!writen (cookie->fd, buffer, size, cookie->is_socket)) - return (gpgrt_ssize_t)size; /* Okay. */ - } - - if (!running_detached && cookie->fd != -1 - && isatty (es_fileno (es_stderr))) - { - if (*cookie->name) - es_fprintf (es_stderr, "error writing to '%s': %s\n", - cookie->name, strerror(errno)); - else - es_fprintf (es_stderr, "error writing to file descriptor %d: %s\n", - cookie->fd, strerror(errno)); - } - if (cookie->is_socket && cookie->fd != -1) - { - sock_close (cookie->fd); - cookie->fd = -1; - log_socket = -1; - } - - return (gpgrt_ssize_t)size; -} - - -static int -fun_closer (void *cookie_arg) -{ - struct fun_cookie_s *cookie = cookie_arg; - - if (cookie->fd != -1 && cookie->fd != 2) - sock_close (cookie->fd); - xfree (cookie); - log_socket = -1; - return 0; -} - - -/* Common function to either set the logging to a file or a file - descriptor. */ -static void -set_file_fd (const char *name, int fd) -{ - estream_t fp; - int want_socket; -#ifdef HAVE_W32CE_SYSTEM - int use_writefile = 0; -#endif - struct fun_cookie_s *cookie; - - /* Close an open log stream. */ - if (logstream) - { - if (logstream != es_stderr) - es_fclose (logstream); - logstream = NULL; - } - - /* Figure out what kind of logging we want. */ - if (name && !strcmp (name, "-")) - { - name = NULL; - fd = es_fileno (es_stderr); - } - - want_socket = 0; - if (name && !strncmp (name, "tcp://", 6) && name[6]) - want_socket = 1; -#ifndef HAVE_W32_SYSTEM - else if (name && !strncmp (name, "socket://", 9)) - want_socket = 2; -#endif /*HAVE_W32_SYSTEM*/ -#ifdef HAVE_W32CE_SYSTEM - else if (name && !strcmp (name, "GPG2:")) - { - HANDLE hd; - - ActivateDevice (L"Drivers\\"GNUPG_NAME"_Log", 0); - /* Ignore a filename and write the debug output to the GPG2: - device. */ - hd = CreateFile (L"GPG2:", GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - fd = (hd == INVALID_HANDLE_VALUE)? -1 : (int)hd; - name = NULL; - force_prefixes = 1; - use_writefile = 1; - } -#endif /*HAVE_W32CE_SYSTEM*/ - - /* Setup a new stream. */ - - /* The xmalloc below is justified because we can expect that this - function is called only during initialization and there is no - easy way out of this error condition. */ - cookie = xmalloc (sizeof *cookie + (name? strlen (name):0)); - strcpy (cookie->name, name? name:""); - cookie->quiet = 0; - cookie->is_socket = 0; - cookie->want_socket = want_socket; -#ifdef HAVE_W32CE_SYSTEM - cookie->use_writefile = use_writefile; -#endif - if (!name) - cookie->fd = fd; - else if (want_socket) - cookie->fd = -1; - else - { - do - cookie->fd = open (name, O_WRONLY|O_APPEND|O_CREAT, - (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH)); - while (cookie->fd == -1 && errno == EINTR); - } - log_socket = cookie->fd; - - { - es_cookie_io_functions_t io = { NULL }; - io.func_write = fun_writer; - io.func_close = fun_closer; - - fp = es_fopencookie (cookie, "w", io); - } - - /* On error default to a stderr based estream. */ - if (!fp) - fp = es_stderr; - - es_setvbuf (fp, NULL, _IOLBF, 0); - - logstream = fp; - - /* We always need to print the prefix and the pid for socket mode, - so that the server reading the socket can do something - meaningful. */ - force_prefixes = want_socket; - - missing_lf = 0; -} - - -/* Set the file to write log to. The special names NULL and "-" may - be used to select stderr and names formatted like - "socket:///home/foo/mylogs" may be used to write the logging to the - socket "/home/foo/mylogs". If the connection to the socket fails - or a write error is detected, the function writes to stderr and - tries the next time again to connect the socket. - */ -void -log_set_file (const char *name) -{ - set_file_fd (name? name: "-", -1); -} - -void -log_set_fd (int fd) -{ - if (! gnupg_fd_valid (fd)) - log_fatal ("logger-fd is invalid: %s\n", strerror (errno)); - - set_file_fd (NULL, fd); -} - - -/* Set a function to retrieve the directory name of a socket if - * only "socket://" has been given to log_set_file. */ -void -log_set_socket_dir_cb (const char *(*fnc)(void)) -{ - socket_dir_cb = fnc; -} - - -void -log_set_pid_suffix_cb (int (*cb)(unsigned long *r_value)) -{ - get_pid_suffix_cb = cb; -} - - -void -log_set_prefix (const char *text, unsigned int flags) -{ - if (text) - { - strncpy (prefix_buffer, text, sizeof (prefix_buffer)-1); - prefix_buffer[sizeof (prefix_buffer)-1] = 0; - } - - with_prefix = (flags & GPGRT_LOG_WITH_PREFIX); - with_time = (flags & GPGRT_LOG_WITH_TIME); - with_pid = (flags & GPGRT_LOG_WITH_PID); - running_detached = (flags & GPGRT_LOG_RUN_DETACHED); -#ifdef HAVE_W32_SYSTEM - no_registry = (flags & GPGRT_LOG_NO_REGISTRY); -#endif -} - - -const char * -log_get_prefix (unsigned int *flags) -{ - if (flags) - { - *flags = 0; - if (with_prefix) - *flags |= GPGRT_LOG_WITH_PREFIX; - if (with_time) - *flags |= GPGRT_LOG_WITH_TIME; - if (with_pid) - *flags |= GPGRT_LOG_WITH_PID; - if (running_detached) - *flags |= GPGRT_LOG_RUN_DETACHED; -#ifdef HAVE_W32_SYSTEM - if (no_registry) - *flags |= GPGRT_LOG_NO_REGISTRY; -#endif - } - return prefix_buffer; -} - -/* This function returns true if the file descriptor FD is in use for - logging. This is preferable over a test using log_get_fd in that - it allows the logging code to use more then one file descriptor. */ -int -log_test_fd (int fd) -{ - if (logstream) - { - int tmp = es_fileno (logstream); - if ( tmp != -1 && tmp == fd) - return 1; - } - if (log_socket != -1 && log_socket == fd) - return 1; - return 0; -} - -int -log_get_fd () -{ - return logstream? es_fileno(logstream) : -1; -} - -estream_t -log_get_stream () -{ - if (!logstream) - { - log_set_file (NULL); /* Make sure a log stream has been set. */ - assert (logstream); - } - return logstream; -} - - -static void -print_prefix (int level, int leading_backspace) -{ - if (level != GPGRT_LOG_CONT) - { /* Note this does not work for multiple line logging as we would - * need to print to a buffer first */ - if (with_time && !force_prefixes) - { - struct tm *tp; - time_t atime = time (NULL); - - tp = localtime (&atime); - es_fprintf_unlocked (logstream, "%04d-%02d-%02d %02d:%02d:%02d ", - 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, - tp->tm_hour, tp->tm_min, tp->tm_sec ); - } - if (with_prefix || force_prefixes) - es_fputs_unlocked (prefix_buffer, logstream); - if (with_pid || force_prefixes) - { - unsigned long pidsuf; - int pidfmt; - - if (get_pid_suffix_cb && (pidfmt=get_pid_suffix_cb (&pidsuf))) - es_fprintf_unlocked (logstream, pidfmt == 1? "[%u.%lu]":"[%u.%lx]", - (unsigned int)getpid (), pidsuf); - else - es_fprintf_unlocked (logstream, "[%u]", (unsigned int)getpid ()); - } - if ((!with_time && (with_prefix || with_pid)) || force_prefixes) - es_putc_unlocked (':', logstream); - /* A leading backspace suppresses the extra space so that we can - correctly output, programname, filename and linenumber. */ - if (!leading_backspace - && (with_time || with_prefix || with_pid || force_prefixes)) - es_putc_unlocked (' ', logstream); - } - - switch (level) - { - case GPGRT_LOG_BEGIN: break; - case GPGRT_LOG_CONT: break; - case GPGRT_LOG_INFO: break; - case GPGRT_LOG_WARN: break; - case GPGRT_LOG_ERROR: break; - case GPGRT_LOG_FATAL: es_fputs_unlocked ("Fatal: ",logstream ); break; - case GPGRT_LOG_BUG: es_fputs_unlocked ("Ohhhh jeeee: ", logstream); break; - case GPGRT_LOG_DEBUG: es_fputs_unlocked ("DBG: ", logstream ); break; - default: - es_fprintf_unlocked (logstream,"[Unknown log level %d]: ", level); - break; - } -} - - -static void -do_logv (int level, int ignore_arg_ptr, const char *extrastring, - const char *prefmt, const char *fmt, va_list arg_ptr) -{ - int leading_backspace = (fmt && *fmt == '\b'); - - if (!logstream) - { -#ifdef HAVE_W32_SYSTEM - char *tmp; - - tmp = (no_registry - ? NULL - : read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, - "DefaultLogFile")); - log_set_file (tmp && *tmp? tmp : NULL); - xfree (tmp); -#else - log_set_file (NULL); /* Make sure a log stream has been set. */ -#endif - assert (logstream); - } - - es_flockfile (logstream); - if (missing_lf && level != GPGRT_LOG_CONT) - es_putc_unlocked ('\n', logstream ); - missing_lf = 0; - - print_prefix (level, leading_backspace); - if (leading_backspace) - fmt++; - - if (fmt) - { - if (prefmt) - es_fputs_unlocked (prefmt, logstream); - - if (ignore_arg_ptr) - { /* This is used by log_string and comes with the extra - * feature that after a LF the next line is indent at the - * length of the prefix. Note that we do not yet include - * the length of the timestamp and pid in the indent - * computation. */ - const char *p, *pend; - - for (p = fmt; (pend = strchr (p, '\n')); p = pend+1) - es_fprintf_unlocked (logstream, "%*s%.*s", - (int)((p != fmt - && (with_prefix || force_prefixes)) - ?strlen (prefix_buffer)+2:0), "", - (int)(pend - p)+1, p); - es_fputs_unlocked (p, logstream); - } - else - es_vfprintf_unlocked (logstream, fmt, arg_ptr); - if (*fmt && fmt[strlen(fmt)-1] != '\n') - missing_lf = 1; - } - - /* If we have an EXTRASTRING print it now while we still hold the - * lock on the logstream. */ - if (extrastring) - { - int c; - - if (missing_lf) - { - es_putc_unlocked ('\n', logstream); - missing_lf = 0; - } - print_prefix (level, leading_backspace); - es_fputs_unlocked (">> ", logstream); - missing_lf = 1; - while ((c = *extrastring++)) - { - missing_lf = 1; - if (c == '\\') - es_fputs_unlocked ("\\\\", logstream); - else if (c == '\r') - es_fputs_unlocked ("\\r", logstream); - else if (c == '\n') - { - es_fputs_unlocked ("\\n\n", logstream); - if (*extrastring) - { - print_prefix (level, leading_backspace); - es_fputs_unlocked (">> ", logstream); - } - else - missing_lf = 0; - } - else - es_putc_unlocked (c, logstream); - } - if (missing_lf) - { - es_putc_unlocked ('\n', logstream); - missing_lf = 0; - } - } - - if (level == GPGRT_LOG_FATAL) - { - if (missing_lf) - es_putc_unlocked ('\n', logstream); - es_funlockfile (logstream); - exit (2); - } - else if (level == GPGRT_LOG_BUG) - { - if (missing_lf) - es_putc_unlocked ('\n', logstream ); - es_funlockfile (logstream); - /* Using backtrace requires a configure test and to pass - * -rdynamic to gcc. Thus we do not enable it now. */ - /* { */ - /* void *btbuf[20]; */ - /* int btidx, btlen; */ - /* char **btstr; */ - - /* btlen = backtrace (btbuf, DIM (btbuf)); */ - /* btstr = backtrace_symbols (btbuf, btlen); */ - /* if (btstr) */ - /* for (btidx=0; btidx < btlen; btidx++) */ - /* log_debug ("[%d] %s\n", btidx, btstr[btidx]); */ - /* } */ - abort (); - } - else - es_funlockfile (logstream); -} - - -void -log_log (int level, const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt) ; - do_logv (level, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); -} - - -void -log_logv (int level, const char *fmt, va_list arg_ptr) -{ - do_logv (level, 0, NULL, NULL, fmt, arg_ptr); -} - - -/* Same as log_logv but PREFIX is printed immediately before FMT. - * Note that PREFIX is an additional string and independent of the - * prefix set by log_set_prefix. */ -void -log_logv_prefix (int level, const char *prefix, - const char *fmt, va_list arg_ptr) -{ - do_logv (level, 0, NULL, prefix, fmt, arg_ptr); -} - - -static void -do_log_ignore_arg (int level, const char *str, ...) -{ - va_list arg_ptr; - va_start (arg_ptr, str); - do_logv (level, 1, NULL, NULL, str, arg_ptr); - va_end (arg_ptr); -} - - -/* Log STRING at LEVEL but indent from the second line on by the - * length of the prefix. */ -void -log_string (int level, const char *string) -{ - /* We need a dummy arg_ptr, but there is no portable way to create - * one. So we call the do_logv function through a variadic wrapper. */ - do_log_ignore_arg (level, string); -} - - -void -log_info (const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_INFO, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); -} - - -void -log_error (const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_ERROR, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); - /* Protect against counter overflow. */ - if (errorcount < 30000) - errorcount++; -} - - -void -log_fatal (const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_FATAL, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); - abort (); /* Never called; just to make the compiler happy. */ -} - - -void -log_bug (const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_BUG, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); - abort (); /* Never called; just to make the compiler happy. */ -} - - -void -log_debug (const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_DEBUG, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); -} - - -/* The same as log_debug but at the end of the output STRING is - * printed with LFs expanded to include the prefix and a final --end-- - * marker. */ -void -log_debug_string (const char *string, const char *fmt, ...) -{ - va_list arg_ptr ; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_DEBUG, 0, string, NULL, fmt, arg_ptr); - va_end (arg_ptr); -} - - -void -log_printf (const char *fmt, ...) -{ - va_list arg_ptr; - - va_start (arg_ptr, fmt); - do_logv (fmt ? GPGRT_LOG_CONT : GPGRT_LOG_BEGIN, 0, NULL, NULL, fmt, arg_ptr); - va_end (arg_ptr); -} - - -/* Flush the log - this is useful to make sure that the trailing - linefeed has been printed. */ -void -log_flush (void) -{ - do_log_ignore_arg (GPGRT_LOG_CONT, NULL); -} - - -/* Print a hexdump of BUFFER. With TEXT of NULL print just the raw - dump, with TEXT just an empty string, print a trailing linefeed, - otherwise print an entire debug line. */ -void -log_printhex (const void *buffer, size_t length, const char *text) -{ - if (text && *text) - log_debug ("%s ", text); - if (length) - { - const unsigned char *p = buffer; - log_printf ("%02X", *p); - for (length--, p++; length--; p++) - log_printf (" %02X", *p); - } - if (text) - log_printf ("\n"); -} - - -/* -void -log_printcanon () {} -is found in sexputils.c -*/ - -/* -void -log_printsexp () {} -is found in sexputils.c -*/ - -/* Print a microsecond timestamp followed by a FORMAT. */ -void -log_clock (const char *fmt, ...) -{ -#if ENABLE_LOG_CLOCK - static unsigned long long initial; - struct timespec tv; - unsigned long long now; - char clockbuf[50]; - va_list arg_ptr; - - if (clock_gettime (CLOCK_REALTIME, &tv)) - { - log_debug ("error getting the realtime clock value\n"); - return; - } - now = tv.tv_sec * 1000000000ull; - now += tv.tv_nsec; - - if (!initial) - initial = now; - - snprintf (clockbuf, sizeof clockbuf, "[%6llu] ", (now - initial)/1000); - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_DEBUG, 0, NULL, clockbuf, fmt, arg_ptr); - va_end (arg_ptr); - -#else /*!ENABLE_LOG_CLOCK*/ - - /* You may need to link with -ltr to use the above code. */ - va_list arg_ptr; - - va_start (arg_ptr, fmt); - do_logv (GPGRT_LOG_DEBUG, 0, NULL, "[no clock] ", fmt, arg_ptr); - va_end (arg_ptr); - -#endif /*!ENABLE_LOG_CLOCK*/ -} - - -#ifdef GPGRT_HAVE_MACRO_FUNCTION -void -bug_at( const char *file, int line, const char *func ) -{ - log_log (GPGRT_LOG_BUG, "... this is a bug (%s:%d:%s)\n", file, line, func); - abort (); /* Never called; just to make the compiler happy. */ -} -#else /*!GPGRT_HAVE_MACRO_FUNCTION*/ -void -bug_at( const char *file, int line ) -{ - log_log (GPGRT_LOG_BUG, "you found a bug ... (%s:%d)\n", file, line); - abort (); /* Never called; just to make the compiler happy. */ -} -#endif /*!GPGRT_HAVE_MACRO_FUNCTION*/ - - -#ifdef GPGRT_HAVE_MACRO_FUNCTION -void -_log_assert (const char *expr, const char *file, int line, const char *func) -{ - log_log (GPGRT_LOG_BUG, "Assertion \"%s\" in %s failed (%s:%d)\n", - expr, func, file, line); - abort (); /* Never called; just to make the compiler happy. */ -} -#else /*!GPGRT_HAVE_MACRO_FUNCTION*/ -void -_log_assert (const char *expr, const char *file, int line) -{ - log_log (GPGRT_LOG_BUG, "Assertion \"%s\" failed (%s:%d)\n", - expr, file, line); - abort (); /* Never called; just to make the compiler happy. */ -} -#endif /*!GPGRT_HAVE_MACRO_FUNCTION*/ - -#endif /* Use our own logging functions. */ diff --git a/common/logging.h b/common/logging.h index a20b8f895..a5800cb4c 100644 --- a/common/logging.h +++ b/common/logging.h @@ -1,180 +1,94 @@ /* logging.h * Copyright (C) 1999, 2000, 2001, 2004, 2006, * 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_LOGGING_H #define GNUPG_COMMON_LOGGING_H #include #include #include #include "mischelp.h" #include "w32help.h" -#if defined(GPGRT_ENABLE_LOG_MACROS) && defined(log_debug_string) - /* We use the libgpg-error provided log functions. but we need one - * more function: */ -# ifdef GPGRT_HAVE_MACRO_FUNCTION +/* We use the libgpg-error provided log functions. but we need one + * more function: */ +#ifdef GPGRT_HAVE_MACRO_FUNCTION # define BUG() bug_at ( __FILE__, __LINE__, __FUNCTION__) static inline void bug_at (const char *file, int line, const char *func) GPGRT_ATTR_NORETURN; static inline void bug_at (const char *file, int line, const char *func) { gpgrt_log (GPGRT_LOGLVL_BUG, "there is a bug at %s:%d:%s\n", file, line, func); abort (); } -# else +#else # define BUG() bug_at ( __FILE__, __LINE__) static inline void bug_at (const char *file, int line) GPGRT_ATTR_NORETURN; static inline void bug_at (const char *file, int line) { gpgrt_log (GPGRT_LOGLVL_BUG, "there is a bug at %s:%d\n", file, line); abort (); } -# endif /*!GPGRT_HAVE_MACRO_FUNCTION*/ - - -#else /* Use gnupg internal logging functions. */ - -int log_get_errorcount (int clear); -void log_inc_errorcount (void); -void log_set_file( const char *name ); -void log_set_fd (int fd); -void log_set_socket_dir_cb (const char *(*fnc)(void)); -void log_set_pid_suffix_cb (int (*cb)(unsigned long *r_value)); -void log_set_prefix (const char *text, unsigned int flags); -const char *log_get_prefix (unsigned int *flags); -int log_test_fd (int fd); -int log_get_fd(void); -estream_t log_get_stream (void); - -#ifdef GPGRT_HAVE_MACRO_FUNCTION - void bug_at (const char *file, int line, const char *func) - GPGRT_ATTR_NORETURN; - void _log_assert (const char *expr, const char *file, int line, - const char *func) GPGRT_ATTR_NORETURN; -# define BUG() bug_at( __FILE__ , __LINE__, __FUNCTION__) -# define log_assert(expr) \ - ((expr) \ - ? (void) 0 \ - : _log_assert (#expr, __FILE__, __LINE__, __FUNCTION__)) -#else /*!GPGRT_HAVE_MACRO_FUNCTION*/ - void bug_at (const char *file, int line); - void _log_assert (const char *expr, const char *file, int line); -# define BUG() bug_at( __FILE__ , __LINE__ ) -# define log_assert(expr) \ - ((expr) \ - ? (void) 0 \ - : _log_assert (#expr, __FILE__, __LINE__)) #endif /*!GPGRT_HAVE_MACRO_FUNCTION*/ -/* Flag values for log_set_prefix. */ -#define GPGRT_LOG_WITH_PREFIX 1 -#define GPGRT_LOG_WITH_TIME 2 -#define GPGRT_LOG_WITH_PID 4 -#define GPGRT_LOG_RUN_DETACHED 256 -#define GPGRT_LOG_NO_REGISTRY 512 - -/* Log levels as used by log_log. */ -enum jnlib_log_levels { - GPGRT_LOG_BEGIN, - GPGRT_LOG_CONT, - GPGRT_LOG_INFO, - GPGRT_LOG_WARN, - GPGRT_LOG_ERROR, - GPGRT_LOG_FATAL, - GPGRT_LOG_BUG, - GPGRT_LOG_DEBUG -}; -#define GPGRT_LOGLVL_BEGIN GPGRT_LOG_BEGIN -#define GPGRT_LOGLVL_CONT GPGRT_LOG_CONT -#define GPGRT_LOGLVL_INFO GPGRT_LOG_INFO -#define GPGRT_LOGLVL_WARN GPGRT_LOG_WARN -#define GPGRT_LOGLVL_ERROR GPGRT_LOG_ERROR -#define GPGRT_LOGLVL_FATAL GPGRT_LOG_FATAL -#define GPGRT_LOGLVL_BUG GPGRT_LOG_BUG -#define GPGRT_LOGLVL_DEBUG GPGRT_LOG_DEBUG - -void log_log (int level, const char *fmt, ...) GPGRT_ATTR_PRINTF(2,3); -void log_logv (int level, const char *fmt, va_list arg_ptr); -void log_logv_prefix (int level, const char *prefix, - const char *fmt, va_list arg_ptr); -void log_string (int level, const char *string); -void log_bug (const char *fmt, ...) GPGRT_ATTR_NR_PRINTF(1,2); -void log_fatal (const char *fmt, ...) GPGRT_ATTR_NR_PRINTF(1,2); -void log_error (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); -void log_info (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); -void log_debug (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); -void log_debug_string (const char *string, const char *fmt, - ...) GPGRT_ATTR_PRINTF(2,3); -void log_printf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); -void log_flush (void); - -/* Print a hexdump of BUFFER. With TEXT passes as NULL print just the - raw dump, with TEXT being an empty string, print a trailing - linefeed, otherwise print an entire debug line with TEXT followed - by the hexdump and a final LF. */ -void log_printhex (const void *buffer, size_t length, const char *text); - -void log_clock (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); -#endif /* Use gnupg internal logging functions. */ /* Some handy assertion macros which don't abort. */ #define return_if_fail(expr) do { \ if (!(expr)) { \ log_debug ("%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return; \ } } while (0) #define return_null_if_fail(expr) do { \ if (!(expr)) { \ log_debug ("%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return NULL; \ } } while (0) #define return_val_if_fail(expr,val) do { \ if (!(expr)) { \ log_debug ("%s:%d: assertion '%s' failed\n", \ __FILE__, __LINE__, #expr ); \ return (val); \ } } while (0) #define never_reached() do { \ log_debug ("%s:%d: oops - should never get here\n", \ __FILE__, __LINE__ ); \ } while (0) #endif /*GNUPG_COMMON_LOGGING_H*/ diff --git a/common/name-value.h b/common/name-value.h index db9270a6c..5c24b8db1 100644 --- a/common/name-value.h +++ b/common/name-value.h @@ -1,120 +1,120 @@ /* name-value.h - Parser and writer for a name-value format. * Copyright (C) 2016 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * 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 . */ #ifndef GNUPG_COMMON_NAME_VALUE_H #define GNUPG_COMMON_NAME_VALUE_H struct name_value_container; typedef struct name_value_container *nvc_t; struct name_value_entry; typedef struct name_value_entry *nve_t; /* Memory management, and dealing with entries. */ /* Allocate a name value container structure. */ nvc_t nvc_new (void); /* Allocate a name value container structure for use with the extended * private key format. */ nvc_t nvc_new_private_key (void); /* Release a name value container structure. */ void nvc_release (nvc_t pk); /* Get the name. */ char *nve_name (nve_t pke); /* Get the value. */ char *nve_value (nve_t pke); /* Lookup and iteration. */ /* Get the first non-comment entry. */ nve_t nvc_first (nvc_t pk); /* Get the first entry with the given name. */ nve_t nvc_lookup (nvc_t pk, const char *name); /* Get the next non-comment entry. */ nve_t nve_next (nve_t entry); /* Get the next entry with the given name. */ nve_t nve_next_value (nve_t entry, const char *name); /* Adding and modifying values. */ /* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it is not updated but the new entry is appended. */ gpg_error_t nvc_add (nvc_t pk, const char *name, const char *value); /* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it is updated with VALUE. If multiple entries with NAME exist, the first entry is updated. */ gpg_error_t nvc_set (nvc_t pk, const char *name, const char *value); /* Delete the given entry from PK. */ void nvc_delete (nvc_t pk, nve_t pke); /* Private key handling. */ /* Get the private key. */ gpg_error_t nvc_get_private_key (nvc_t pk, gcry_sexp_t *retsexp); /* Set the private key. */ gpg_error_t nvc_set_private_key (nvc_t pk, gcry_sexp_t sexp); /* Parsing and serialization. */ -/* Parse STREAM and return a newly allocated private key container +/* Parse STREAM and return a newly allocated name-value container structure in RESULT. If ERRLINEP is given, the line number the parser was last considering is stored there. */ gpg_error_t nvc_parse (nvc_t *result, int *errlinep, estream_t stream); /* Parse STREAM and return a newly allocated name value container structure in RESULT - assuming the extended private key format. If ERRLINEP is given, the line number the parser was last considering is stored there. */ gpg_error_t nvc_parse_private_key (nvc_t *result, int *errlinep, estream_t stream); /* Write a representation of PK to STREAM. */ gpg_error_t nvc_write (nvc_t pk, estream_t stream); #endif /* GNUPG_COMMON_NAME_VALUE_H */ diff --git a/common/percent.c b/common/percent.c index 569c5fd99..eeb026fbe 100644 --- a/common/percent.c +++ b/common/percent.c @@ -1,238 +1,282 @@ /* percent.c - Percent escaping * Copyright (C) 2008, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include "util.h" /* Create a newly alloced string from STRING with all spaces and control characters converted to plus signs or %xx sequences. The function returns the new string or NULL in case of a malloc failure. Note that we also escape the quote character to work around a bug in the mingw32 runtime which does not correcty handle command line quoting. We correctly double the quote mark when calling a program (i.e. gpg-protect-tool), but the pre-main code does not notice the double quote as an escaped quote. We do this also on POSIX systems for consistency. */ char * percent_plus_escape (const char *string) { char *buffer, *p; const char *s; size_t length; for (length=1, s=string; *s; s++) { if (*s == '+' || *s == '\"' || *s == '%' || *(const unsigned char *)s < 0x20) length += 3; else length++; } buffer = p = xtrymalloc (length); if (!buffer) return NULL; for (s=string; *s; s++) { if (*s == '+' || *s == '\"' || *s == '%' || *(const unsigned char *)s < 0x20) { snprintf (p, 4, "%%%02X", *(unsigned char *)s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; return buffer; } +/* Create a newly alloced string from (DATA,DATALEN) with embedded + * Nuls quoted as %00. The standard percent unescaping can be + * used to reverse this encoding. */ +char * +percent_data_escape (const void *data, size_t datalen) +{ + char *buffer, *p; + const char *s; + size_t n, length; + + for (length=1, s=data, n=datalen; n; s++, n--) + { + if (!*s || *s == '%') + length += 3; + else + length++; + } + + buffer = p = xtrymalloc (length); + if (!buffer) + return NULL; + + for (s=data, n=datalen; n; s++, n--) + { + if (!*s) + { + memcpy (p, "%00", 3); + p += 3; + } + else if (*s == '%') + { + memcpy (p, "%25", 3); + p += 3; + } + else + *p++ = *s; + } + *p = 0; + + return buffer; + +} + + /* Do the percent and plus/space unescaping from STRING to BUFFER and return the length of the valid buffer. Plus unescaping is only done if WITHPLUS is true. An escaped Nul character will be replaced by NULREPL. */ static size_t do_unescape (unsigned char *buffer, const unsigned char *string, int withplus, int nulrepl) { unsigned char *p = buffer; while (*string) { if (*string == '%' && string[1] && string[2]) { string++; *p = xtoi_2 (string); if (!*p) *p = nulrepl; string++; } else if (*string == '+' && withplus) *p = ' '; else *p = *string; p++; string++; } return (p - buffer); } /* Count space required after unescaping STRING. Note that this will never be larger than strlen (STRING). */ static size_t count_unescape (const unsigned char *string) { size_t n = 0; while (*string) { if (*string == '%' && string[1] && string[2]) { string++; string++; } string++; n++; } return n; } /* Helper. */ static char * do_plus_or_plain_unescape (const char *string, int withplus, int nulrepl) { size_t nbytes, n; char *newstring; nbytes = count_unescape (string); newstring = xtrymalloc (nbytes+1); if (newstring) { n = do_unescape (newstring, string, withplus, nulrepl); assert (n == nbytes); newstring[n] = 0; } return newstring; } /* Create a new allocated string from STRING with all "%xx" sequences decoded and all plus signs replaced by a space. Embedded Nul characters are replaced by the value of NULREPL. The function returns the new string or NULL in case of a malloc failure. */ char * percent_plus_unescape (const char *string, int nulrepl) { return do_plus_or_plain_unescape (string, 1, nulrepl); } /* Create a new allocated string from STRING with all "%xx" sequences decoded. Embedded Nul characters are replaced by the value of NULREPL. The function returns the new string or NULL in case of a malloc failure. */ char * percent_unescape (const char *string, int nulrepl) { return do_plus_or_plain_unescape (string, 0, nulrepl); } static size_t do_unescape_inplace (char *string, int withplus, int nulrepl) { unsigned char *p, *p0; p = p0 = string; while (*string) { if (*string == '%' && string[1] && string[2]) { string++; *p = xtoi_2 (string); if (!*p) *p = nulrepl; string++; } else if (*string == '+' && withplus) *p = ' '; else *p = *string; p++; string++; } return (p - p0); } /* Perform percent and plus unescaping in STRING and return the new valid length of the string. Embedded Nul characters are replaced by the value of NULREPL. A terminating Nul character is not inserted; the caller might want to call this function this way: foo[percent_plus_unescape_inplace (foo, 0)] = 0; */ size_t percent_plus_unescape_inplace (char *string, int nulrepl) { return do_unescape_inplace (string, 1, nulrepl); } /* Perform percent unescaping in STRING and return the new valid length of the string. Embedded Nul characters are replaced by the value of NULREPL. A terminating Nul character is not inserted; the caller might want to call this function this way: foo[percent_unescape_inplace (foo, 0)] = 0; */ size_t percent_unescape_inplace (char *string, int nulrepl) { return do_unescape_inplace (string, 0, nulrepl); } diff --git a/common/t-percent.c b/common/t-percent.c index 145a89bf3..94ece9249 100644 --- a/common/t-percent.c +++ b/common/t-percent.c @@ -1,114 +1,163 @@ /* t-percent.c - Module test for percent.c * Copyright (C) 2008 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #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)); \ exit (1); \ } while(0) static void test_percent_plus_escape (void) { static struct { const char *string; const char *expect; } tbl[] = { { "", "" }, { "a", "a", }, { " ", "+", }, { " ", "++" }, { "+ +", "%2B+%2B" }, { "\" \"", "%22+%22" }, { "%22", "%2522" }, { "%% ", "%25%25+" }, { "\n ABC\t", "%0A+ABC%09" }, { NULL, NULL } }; char *buf, *buf2; int i; size_t len; for (i=0; tbl[i].string; i++) { buf = percent_plus_escape (tbl[i].string); if (!buf) { fprintf (stderr, "out of core: %s\n", strerror (errno)); exit (2); } if (strcmp (buf, tbl[i].expect)) fail (i); buf2 = percent_plus_unescape (buf, 0); if (!buf2) { fprintf (stderr, "out of core: %s\n", strerror (errno)); exit (2); } if (strcmp (buf2, tbl[i].string)) fail (i); xfree (buf2); /* Now test the inplace conversion. */ len = percent_plus_unescape_inplace (buf, 0); buf[len] = 0; if (strcmp (buf, tbl[i].string)) fail (i); xfree (buf); } } +static void +test_percent_data_escape (void) +{ + static struct { + const char *data; + size_t datalen; + const char *expect; + } tbl[] = { + { + "", 0, + "" + }, { + "a", 1, + "a", + }, { + "%22", 3, + "%2522" + }, { + "%%", 3, + "%25%25%00" + }, { + "\n \0BC\t", 6, + "\n %00BC\t" + }, { NULL, 0, NULL } + }; + char *buf; + int i; + size_t len; + + for (i=0; tbl[i].data; i++) + { + buf = percent_data_escape (tbl[i].data, tbl[i].datalen); + if (!buf) + { + fprintf (stderr, "out of core: %s\n", strerror (errno)); + exit (2); + } + if (strcmp (buf, tbl[i].expect)) + fail (i); + len = percent_plus_unescape_inplace (buf, 0); + if (len != tbl[i].datalen) + fail (i); + else if (memcmp (buf, tbl[i].data, tbl[i].datalen)) + fail (i); + xfree (buf); + } +} + + int main (int argc, char **argv) { (void)argc; (void)argv; /* FIXME: We escape_unescape is not tested - only percent_plus_unescape. */ test_percent_plus_escape (); - + test_percent_data_escape (); return 0; } diff --git a/common/util.h b/common/util.h index f3722812d..682415d92 100644 --- a/common/util.h +++ b/common/util.h @@ -1,385 +1,371 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * GnuPG is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t and estream. */ /* These error codes are used but not defined in the required * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ -#if GPG_ERROR_VERSION_NUMBER < 0x011a00 /* 1.26 */ -# define GPG_ERR_UNKNOWN_FLAG 309 -# define GPG_ERR_INV_ORDER 310 -# define GPG_ERR_ALREADY_FETCHED 311 -# define GPG_ERR_TRY_LATER 312 -# define GPG_ERR_SYSTEM_BUG 666 -# define GPG_ERR_DNS_UNKNOWN 711 -# define GPG_ERR_DNS_SECTION 712 -# define GPG_ERR_DNS_ADDRESS 713 -# define GPG_ERR_DNS_NO_QUERY 714 -# define GPG_ERR_DNS_NO_ANSWER 715 -# define GPG_ERR_DNS_CLOSED 716 -# define GPG_ERR_DNS_VERIFY 717 -# define GPG_ERR_DNS_TIMEOUT 718 -#endif /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* The length of the keygrip. This is a SHA-1 hash of the key * parameters as generated by gcry_pk_get_keygrip. */ #define KEYGRIP_LEN 20 /* Get all the stuff from jnlib. */ #include "../common/logging.h" #include "../common/argparse.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "../common/fwddecl.h" #include "../common/utilproto.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #undef snprintf #define snprintf gpgrt_snprintf /* Replacements for macros not available with libgpg-error < 1.20. */ /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xfree_fnc gcry_free #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Defined by init.c. */ extern gpg_err_source_t default_errsource; /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidentally ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, 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, const unsigned char *sexp, size_t sexplen); void log_printsexp (const char *text, gcry_sexp_t sexp); gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); /*-- percent.c --*/ char *percent_plus_escape (const char *string); +char *percent_data_escape (const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); char *openpgp_oid_to_str (gcry_mpi_t a); int openpgp_oid_is_ed25519 (gcry_mpi_t a); int openpgp_oid_is_cv25519 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits); const char *openpgp_oid_to_curve (const char *oid, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, int *r_algo, unsigned int *r_nbits); /*-- homedir.c --*/ const char *standard_homedir (void); const char *default_homedir (void); void gnupg_set_homedir (const char *newdir); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); const char *gnupg_daemon_rootdir (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gnupg_cachedir (void); const char *dirmngr_socket_name (void); char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Print an out of core message and die. */ void xoutofcore (void); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* Replacement for gcry_cipher_algo_name. */ const char *gnupg_cipher_algo_name (int algo); void obsolete_option (const char *configname, unsigned int configlineno, const char *name); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_utf8_buffer3 (estream_t fp, const void *p, size_t n, const char *delim); void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_utf8_string (estream_t stream, const char *p); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); 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); int is_file_compressed (const char *s, int *ret_rc); int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); struct debug_flags_s { unsigned int flag; const char *name; }; int parse_debug_flag (const char *string, unsigned int *debugvar, const struct debug_flags_s *flags); /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts which an extisting 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. */ static inline char * _gnupg_ttyname (int fd) { (void)fd; return NULL; } #else /*HAVE_TTYNAME*/ # define gnupg_ttyname(n) ttyname ((n)) #endif /*HAVE_TTYNAME */ #ifdef HAVE_W32CE_SYSTEM #define getpid() GetCurrentProcessId () char *_gnupg_getenv (const char *name); /* See sysutils.c */ #define getenv(a) _gnupg_getenv ((a)) char *_gnupg_setenv (const char *name); /* See sysutils.c */ #define setenv(a,b,c) _gnupg_setenv ((a),(b),(c)) int _gnupg_isatty (int fd); #define gnupg_isatty(a) _gnupg_isatty ((a)) #else #define gnupg_isatty(a) isatty ((a)) #endif /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \ || (*(p) >= 'a' && *(p) <= 'z')) #define alnump(p) (alphap (p) || digitp (p)) #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/configure.ac b/configure.ac index 0665115dc..78a03c420 100644 --- a/configure.ac +++ b/configure.ac @@ -1,2103 +1,2107 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998-2018 Free Software Foundation, Inc. # Copyright (C) 1998-2018 Werner Koch # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GnuPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) min_automake_version="1.14" # To build a release you need to create a tag with the version number # (git tag -s gnupg-2.n.m) and run "./autogen.sh --force". Please # bump the version number immediately *after* the release and do # another commit and push so that the git magic is able to work. m4_define([mym4_package],[gnupg]) m4_define([mym4_major], [2]) m4_define([mym4_minor], [3]) m4_define([mym4_micro], [0]) # 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 # with an annotated tag. For example the 2.1 branch starts off with # the tag "gnupg-2.1-base". This is used as the base for counting # beta numbers before the first release of a series. # Below is m4 magic to extract and compute the git revision number, # the decimalized short revision number, a beta version string and a # flag indicating a development version (mym4_isbeta). Note that the # m4 processing is done by autoconf and not during the configure run. m4_define([mym4_verslist], m4_split(m4_esyscmd([./autogen.sh --find-version] \ mym4_package mym4_major mym4_minor mym4_micro),[:])) 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_esyscmd([echo ]mym4_version[>VERSION]) AC_INIT([mym4_package],[mym4_version], [https://bugs.gnupg.org]) # When changing the SWDB tag please also adjust the hard coded tags in # build-aux/speedo.mk and Makefile.am AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg24", [swdb tag for this branch]) -NEED_GPG_ERROR_VERSION=1.24 +NEED_GPG_ERROR_VERSION=1.29 NEED_LIBGCRYPT_API=1 NEED_LIBGCRYPT_VERSION=1.7.0 NEED_LIBASSUAN_API=2 NEED_LIBASSUAN_VERSION=2.5.0 NEED_KSBA_API=1 NEED_KSBA_VERSION=1.3.4 NEED_NTBTLS_API=1 NEED_NTBTLS_VERSION=0.1.0 NEED_NPTH_API=1 NEED_NPTH_VERSION=1.2 NEED_GNUTLS_VERSION=3.0 NEED_SQLITE_VERSION=3.7 development_version=mym4_isbeta PACKAGE=$PACKAGE_NAME PACKAGE_GT=${PACKAGE_NAME}2 VERSION=$PACKAGE_VERSION AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([sm/gpgsm.c]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip]) AC_CANONICAL_HOST AB_INIT AC_GNU_SOURCE # Some status variables. have_gpg_error=no have_libgcrypt=no have_libassuan=no have_ksba=no have_ntbtls=no have_gnutls=no have_sqlite=no have_npth=no have_libusb=no have_system_resolver=no gnupg_have_ldap="n/a" use_zip=yes use_bzip2=yes use_exec=yes use_trust_models=yes use_tofu=yes use_libdns=yes card_support=yes use_ccid_driver=auto dirmngr_auto_start=yes use_tls_library=no large_secmem=no show_tor_support=no # gpg is a required part and can't be disabled anymore. build_gpg=yes GNUPG_BUILD_PROGRAM(gpgsm, yes) # The agent is a required part and can't be disabled anymore. build_agent=yes GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(g13, no) GNUPG_BUILD_PROGRAM(dirmngr, yes) GNUPG_BUILD_PROGRAM(doc, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) # We use gpgtar to unpack test data, hence we always build it. If the # user opts out, we simply don't install it. GNUPG_BUILD_PROGRAM(gpgtar, yes) GNUPG_BUILD_PROGRAM(wks-tools, no) AC_SUBST(PACKAGE) AC_SUBST(PACKAGE_GT) AC_SUBST(VERSION) AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package]) AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT", [Name of this package for gettext]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package]) AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT", [Bug report address]) AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION", [Required version of Libgcrypt]) AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION", [Required version of Libksba]) AC_DEFINE_UNQUOTED(NEED_NTBTLS_VERSION, "$NEED_NTBTLS_VERSION", [Required version of NTBTLS]) # The default is to use the modules from this package and the few # other packages in a standard place; i.e where this package gets # installed. With these options it is possible to override these # ${prefix} depended values with fixed paths, which can't be replaced # at make time. See also am/cmacros.am and the defaults in AH_BOTTOM. AC_ARG_WITH(agent-pgm, [ --with-agent-pgm=PATH Use PATH as the default for the agent)], GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" ) AC_SUBST(GNUPG_AGENT_PGM) AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM") show_gnupg_agent_pgm="(default)" test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM" AC_ARG_WITH(pinentry-pgm, [ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)], GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" ) AC_SUBST(GNUPG_PINENTRY_PGM) AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM") show_gnupg_pinentry_pgm="(default)" test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM" AC_ARG_WITH(scdaemon-pgm, [ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)], GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" ) AC_SUBST(GNUPG_SCDAEMON_PGM) AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM") show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM") show_gnupg_dirmngr_pgm="(default)" test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM" AC_ARG_WITH(protect-tool-pgm, [ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)], GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" ) AC_SUBST(GNUPG_PROTECT_TOOL_PGM) AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM") show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" AC_ARG_WITH(dirmngr-ldap-pgm, [ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmngr ldap wrapper)], GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" ) AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM) AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM") show_gnupg_dirmngr_ldap_pgm="(default)" 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, AC_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 AC_MSG_CHECKING([whether SELinux support is requested]) AC_ARG_ENABLE(selinux-support, AC_HELP_STRING([--enable-selinux-support], [enable SELinux support]), selinux_support=$enableval, selinux_support=no) AC_MSG_RESULT($selinux_support) AC_MSG_CHECKING([whether to allocate extra secure memory]) AC_ARG_ENABLE(large-secmem, AC_HELP_STRING([--enable-large-secmem], [allocate extra secure memory]), large_secmem=$enableval, large_secmem=no) AC_MSG_RESULT($large_secmem) if test "$large_secmem" = yes ; then SECMEM_BUFFER_SIZE=65536 else SECMEM_BUFFER_SIZE=32768 fi AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE, [Size of secure memory buffer]) AC_MSG_CHECKING([calibrated passphrase-stretching (s2k) duration]) AC_ARG_WITH(agent-s2k-calibration, AC_HELP_STRING([--with-agent-s2k-calibration=MSEC], [calibrate passphrase stretching (s2k) to MSEC milliseconds]), agent_s2k_calibration=$withval, agent_s2k_calibration=100) AC_MSG_RESULT($agent_s2k_calibration milliseconds) AC_DEFINE_UNQUOTED(AGENT_S2K_CALIBRATION, $agent_s2k_calibration, [Agent s2k calibration time (ms)]) AC_MSG_CHECKING([whether to enable trust models]) AC_ARG_ENABLE(trust-models, AC_HELP_STRING([--disable-trust-models], [disable all trust models except "always"]), use_trust_models=$enableval) AC_MSG_RESULT($use_trust_models) if test "$use_trust_models" = no ; then AC_DEFINE(NO_TRUST_MODELS, 1, [Define to include only trust-model always]) fi AC_MSG_CHECKING([whether to enable TOFU]) AC_ARG_ENABLE(tofu, AC_HELP_STRING([--disable-tofu], [disable the TOFU trust model]), use_tofu=$enableval, use_tofu=$use_trust_models) AC_MSG_RESULT($use_tofu) if test "$use_trust_models" = no && test "$use_tofu" = yes; then AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given]) fi AC_MSG_CHECKING([whether to enable libdns]) AC_ARG_ENABLE(libdns, AC_HELP_STRING([--disable-libdns], [do not build with libdns support]), use_libdns=$enableval, use_libdns=yes) AC_MSG_RESULT($use_libdns) if test x"$use_libdns" = xyes ; then AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support]) fi AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes) # # Options to disable algorithm # GNUPG_GPG_DISABLE_ALGO([rsa],[RSA public key]) # Elgamal is a MUST algorithm # DSA is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([ecdh],[ECDH public key]) GNUPG_GPG_DISABLE_ALGO([ecdsa],[ECDSA public key]) GNUPG_GPG_DISABLE_ALGO([eddsa],[EdDSA public key]) GNUPG_GPG_DISABLE_ALGO([idea],[IDEA cipher]) # 3DES is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([cast5],[CAST5 cipher]) GNUPG_GPG_DISABLE_ALGO([blowfish],[BLOWFISH cipher]) GNUPG_GPG_DISABLE_ALGO([aes128],[AES128 cipher]) GNUPG_GPG_DISABLE_ALGO([aes192],[AES192 cipher]) GNUPG_GPG_DISABLE_ALGO([aes256],[AES256 cipher]) GNUPG_GPG_DISABLE_ALGO([twofish],[TWOFISH cipher]) GNUPG_GPG_DISABLE_ALGO([camellia128],[CAMELLIA128 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia192],[CAMELLIA192 cipher]) GNUPG_GPG_DISABLE_ALGO([camellia256],[CAMELLIA256 cipher]) GNUPG_GPG_DISABLE_ALGO([md5],[MD5 hash]) # SHA1 is a MUST algorithm GNUPG_GPG_DISABLE_ALGO([rmd160],[RIPE-MD160 hash]) GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash]) # SHA256 is a MUST algorithm for GnuPG. GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash]) GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash]) # Allow disabling of zip support. # This is in general not a good idea because according to rfc4880 OpenPGP # implementations SHOULD support ZLIB. AC_MSG_CHECKING([whether to enable the ZIP and ZLIB compression algorithm]) AC_ARG_ENABLE(zip, AC_HELP_STRING([--disable-zip], [disable the ZIP and ZLIB compression algorithm]), use_zip=$enableval) AC_MSG_RESULT($use_zip) # Allow disabling of bzib2 support. # It is defined only after we confirm the library is available later AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm]) AC_ARG_ENABLE(bzip2, AC_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]), use_bzip2=$enableval) AC_MSG_RESULT($use_bzip2) # Configure option to allow or disallow execution of external # programs, like a photo viewer. AC_MSG_CHECKING([whether to enable external program execution]) AC_ARG_ENABLE(exec, AC_HELP_STRING([--disable-exec],[disable all external program execution]), use_exec=$enableval) AC_MSG_RESULT($use_exec) if test "$use_exec" = no ; then AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution]) fi if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether to enable photo ID viewing]) AC_ARG_ENABLE(photo-viewers, [ --disable-photo-viewers disable photo ID viewers], [if test "$enableval" = no ; then AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing]) fi],enableval=yes) gnupg_cv_enable_photo_viewers=$enableval AC_MSG_RESULT($enableval) if test "$gnupg_cv_enable_photo_viewers" = yes ; then AC_MSG_CHECKING([whether to use a fixed photo ID viewer]) AC_ARG_WITH(photo-viewer, [ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer], [if test "$withval" = yes ; then withval=no elif test "$withval" != no ; then AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval", [if set, restrict photo-viewer to this]) fi],withval=no) AC_MSG_RESULT($withval) fi fi # # Check for the key/uid cache size. This can't be zero, but can be # pretty small on embedded systems. This is used for the gpg part. # AC_MSG_CHECKING([for the size of the key and uid cache]) AC_ARG_ENABLE(key-cache, AC_HELP_STRING([--enable-key-cache=SIZE], [Set key cache to SIZE (default 4096)]),,enableval=4096) if test "$enableval" = "no"; then enableval=5 elif test "$enableval" = "yes" || test "$enableval" = ""; then enableval=4096 fi changequote(,)dnl key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'` changequote([,])dnl if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then AC_MSG_ERROR([invalid key-cache size]) fi AC_MSG_RESULT($key_cache_size) AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size, [Size of the key and UID caches]) # # Check whether we want to use Linux capabilities # AC_MSG_CHECKING([whether use of capabilities is requested]) AC_ARG_WITH(capabilities, [ --with-capabilities use linux capabilities [default=no]], [use_capabilities="$withval"],[use_capabilities=no]) AC_MSG_RESULT($use_capabilities) # # Check whether to disable the card support AC_MSG_CHECKING([whether smartcard support is requested]) AC_ARG_ENABLE(card-support, AC_HELP_STRING([--disable-card-support], [disable smartcard support]), card_support=$enableval) AC_MSG_RESULT($card_support) if test "$card_support" = yes ; then AC_DEFINE(ENABLE_CARD_SUPPORT,1,[Define to include smartcard support]) else build_scdaemon=no fi # # Allow disabling of internal CCID support. # It is defined only after we confirm the library is available later # AC_MSG_CHECKING([whether to enable the internal CCID driver]) AC_ARG_ENABLE(ccid-driver, AC_HELP_STRING([--disable-ccid-driver], [disable the internal CCID driver]), use_ccid_driver=$enableval) AC_MSG_RESULT($use_ccid_driver) AC_MSG_CHECKING([whether to auto start dirmngr]) AC_ARG_ENABLE(dirmngr-auto-start, AC_HELP_STRING([--disable-dirmngr-auto-start], [disable auto starting of the dirmngr]), dirmngr_auto_start=$enableval) AC_MSG_RESULT($dirmngr_auto_start) if test "$dirmngr_auto_start" = yes ; then AC_DEFINE(USE_DIRMNGR_AUTO_START,1, [Define to enable auto starting of the dirmngr]) fi # # To avoid double inclusion of config.h which might happen at some # places, we add the usual double inclusion protection at the top of # config.h. # AH_TOP([ #ifndef GNUPG_CONFIG_H_INCLUDED #define GNUPG_CONFIG_H_INCLUDED ]) # # Stuff which goes at the bottom of config.h. # AH_BOTTOM([ /* This is the major version number of GnuPG so that source included files can test for this. Note, that we use 2 here even for GnuPG 1.9.x. */ #define GNUPG_MAJOR_VERSION 2 /* Now to separate file name parts. Please note that the string version must not contain more than one character because the code assumes strlen()==1 */ #ifdef HAVE_DOSISH_SYSTEM #define DIRSEP_C '\\' #define DIRSEP_S "\\" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ';' #define PATHSEP_S ";" #define EXEEXT_S ".exe" #else #define DIRSEP_C '/' #define DIRSEP_S "/" #define EXTSEP_C '.' #define EXTSEP_S "." #define PATHSEP_C ':' #define PATHSEP_S ":" #define EXEEXT_S "" #endif /* This is the same as VERSION, but should be overridden if the platform cannot handle things like dots '.' in filenames. Set SAFE_VERSION_DOT and SAFE_VERSION_DASH to whatever SAFE_VERSION uses for dots and dashes. */ #define SAFE_VERSION VERSION #define SAFE_VERSION_DOT '.' #define SAFE_VERSION_DASH '-' /* Some global constants. * Note that the homedir must not end in a slash. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" # else # define GNUPG_DEFAULT_HOMEDIR "/gnupg" # endif #elif defined(__VMS) #define GNUPG_DEFAULT_HOMEDIR "/SYS$LOGIN/gnupg" #else #define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" #endif #define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d" #define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d" +#define GNUPG_DEF_COPYRIGHT_LINE \ + "Copyright (C) 2018 Free Software Foundation, Inc." + /* For some systems (DOS currently), we hardcode the path here. For POSIX systems the values are constructed by the Makefiles, so that the values may be overridden by the make invocations; this is to comply with the GNU coding standards. Note that these values are only defaults. */ #ifdef HAVE_DOSISH_SYSTEM # ifdef HAVE_DRIVE_LETTERS # define GNUPG_BINDIR "c:\\gnupg" # define GNUPG_LIBEXECDIR "c:\\gnupg" # define GNUPG_LIBDIR "c:\\gnupg" # define GNUPG_DATADIR "c:\\gnupg" # define GNUPG_SYSCONFDIR "c:\\gnupg" # else # define GNUPG_BINDIR "\\gnupg" # define GNUPG_LIBEXECDIR "\\gnupg" # define GNUPG_LIBDIR "\\gnupg" # define GNUPG_DATADIR "\\gnupg" # define GNUPG_SYSCONFDIR "\\gnupg" # endif #endif /* Derive some other constants. */ #if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID)) #define EXEC_TEMPFILE_ONLY #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 /* Hack used for W32: ldap.m4 also tests for the ASCII version of ldap_start_tls_s because that is the actual symbol used in the library. winldap.h redefines it to our commonly used value, thus we define our usual macro here. */ #ifdef HAVE_LDAP_START_TLS_SA # ifndef HAVE_LDAP_START_TLS_S # define HAVE_LDAP_START_TLS_S 1 # endif #endif /* Enable the es_ macros from gpgrt. */ #define GPGRT_ENABLE_ES_MACROS 1 /* Enable the log_ macros from gpgrt. */ #define GPGRT_ENABLE_LOG_MACROS 1 /* Tell libgcrypt not to use its own libgpg-error implementation. */ #define USE_LIBGPG_ERROR 1 /* Tell Libgcrypt not to include deprecated definitions. */ #define GCRYPT_NO_DEPRECATED 1 /* Our HTTP code is used in estream mode. */ #define HTTP_USE_ESTREAM 1 /* Under W32 we do an explicit socket initialization, thus we need to avoid the on-demand initialization which would also install an atexit handler. */ #define HTTP_NO_WSASTARTUP /* 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*/ ]) AM_MAINTAINER_MODE AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory]) # Checks for programs. AC_MSG_NOTICE([checking for programs]) AC_PROG_MAKE_SET AM_SANITY_CHECK missing_dir=`cd $ac_aux_dir && pwd` AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir) AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir) AM_SILENT_RULES AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AM_PROG_CC_C_O if test "x$ac_cv_prog_cc_c89" = "xno" ; then AC_MSG_ERROR([[No C-89 compiler found]]) fi AC_PROG_INSTALL AC_PROG_LN_S 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_ISC_POSIX AC_SYS_LARGEFILE GNUPG_CHECK_USTAR # We need to compile and run a program on the build machine. A # comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in # the AC archive is broken for autoconf 2.57. Given that there is no # newer version of that macro, we assume that it is also broken for # autoconf 2.61 and thus we use a simple but usually sufficient # approach. AC_MSG_CHECKING(for cc for build) if test "$cross_compiling" = "yes"; then CC_FOR_BUILD="${CC_FOR_BUILD-cc}" else CC_FOR_BUILD="${CC_FOR_BUILD-$CC}" fi AC_MSG_RESULT($CC_FOR_BUILD) AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler]) # We need to call this macro because other pkg-config macros are # not always used. PKG_PROG_PKG_CONFIG try_gettext=yes require_iconv=yes have_dosish_system=no have_w32_system=no have_w32ce_system=no have_android_system=no use_simple_gettext=no use_ldapwrapper=yes mmap_needed=yes require_pipe_to_unblock_pselect=yes case "${host}" in *-mingw32*) # special stuff for Windoze NT ac_cv_have_dev_random=no AC_DEFINE(USE_ONLY_8DOT3,1, [Set this to limit filenames to the 8.3 format]) AC_DEFINE(USE_SIMPLE_GETTEXT,1, [Because the Unix gettext has too much overhead on MingW32 systems and these systems lack Posix functions, we use a simplified version of gettext]) have_dosish_system=yes have_w32_system=yes require_iconv=no require_pipe_to_unblock_pselect=no case "${host}" in *-mingw32ce*) have_w32ce_system=yes ;; *) AC_DEFINE(HAVE_DRIVE_LETTERS,1, [Defined if the OS supports drive letters.]) ;; esac try_gettext="no" use_simple_gettext=yes mmap_needed=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; i?86-*-msdosdjgpp*) # DOS with the DJGPP environment ac_cv_have_dev_random=no AC_DEFINE(HAVE_DRIVE_LETTERS) have_dosish_system=yes try_gettext="no" ;; *-*-hpux*) if test -z "$GCC" ; then CFLAGS="-Ae -D_HPUX_SOURCE $CFLAGS" fi ;; *-dec-osf4*) if test -z "$GCC" ; then # Suppress all warnings # to get rid of the unsigned/signed char mismatch warnings. CFLAGS="-w $CFLAGS" fi ;; *-dec-osf5*) if test -z "$GCC" ; then # Use the newer compiler `-msg_disable ptrmismatch1' to # get rid of the unsigned/signed char mismatch warnings. # Using this may hide other pointer mismatch warnings, but # it at least lets other warning classes through CFLAGS="-msg_disable ptrmismatch1 $CFLAGS" fi ;; m68k-atari-mint) ;; *-linux-android*) have_android_system=yes # Android is fully utf-8 and we do not want to use iconv to # keeps things simple require_iconv=no ;; *-apple-darwin*) AC_DEFINE(_DARWIN_C_SOURCE, 900000L, Expose all libc features (__DARWIN_C_FULL).) ;; *-*-netbsd*) require_pipe_to_unblock_pselect=yes ;; *) ;; esac if test "$require_pipe_to_unblock_pselect" = yes; then AC_DEFINE(HAVE_PSELECT_NO_EINTR, 1, [Defined if we run on systems like NetBSD, where pselect cannot be unblocked by signal from a thread within the same process. We use pipe in this case, instead.]) fi if test "$have_dosish_system" = yes; then AC_DEFINE(HAVE_DOSISH_SYSTEM,1, [Defined if we run on some of the PCDOS like systems (DOS, Windoze. OS/2) with special properties like no file modes, case insensitive file names and preferred use of backslashes as directory name separators.]) fi AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes) AM_CONDITIONAL(USE_SIMPLE_GETTEXT, test x"$use_simple_gettext" = xyes) if test "$have_w32_system" = yes; then AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system]) if test "$have_w32ce_system" = yes; then AC_DEFINE(HAVE_W32CE_SYSTEM,1,[Defined if we run on WindowsCE]) fi fi AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes) AM_CONDITIONAL(HAVE_W32CE_SYSTEM, test "$have_w32ce_system" = yes) if test "$have_android_system" = yes; then AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system]) fi AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes) # (These need to go after AC_PROG_CC so that $EXEEXT is defined) AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any]) # # Checks for libraries. # AC_MSG_NOTICE([checking for libraries]) # # libgpg-error is a library with error codes shared between GnuPG # related projects. # AM_PATH_GPG_ERROR("$NEED_GPG_ERROR_VERSION", have_gpg_error=yes,have_gpg_error=no) # # Libgcrypt is our generic crypto library # AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION", have_libgcrypt=yes,have_libgcrypt=no) # # libassuan is used for IPC # AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION", have_libassuan=yes,have_libassuan=no) if test "$have_libassuan" = "yes"; then AC_DEFINE_UNQUOTED(GNUPG_LIBASSUAN_VERSION, "$libassuan_version", [version of the libassuan library]) show_tor_support="only .onion" fi # # libksba is our X.509 support library # AM_PATH_KSBA("$NEED_KSBA_API:$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no) # # libusb allows us to use the integrated CCID smartcard reader driver. # # FiXME: Use GNUPG_CHECK_LIBUSB and modify to use separate AC_SUBSTs. if test "$use_ccid_driver" = auto || test "$use_ccid_driver" = yes; then case "${host}" in *-mingw32*) LIBUSB_NAME= LIBUSB_LIBS= LIBUSB_CPPFLAGS= ;; *-*-darwin*) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS="-Wl,-framework,CoreFoundation -Wl,-framework,IOKit" ;; *-*-freebsd*) # FreeBSD has a native 1.0 compatible library by -lusb. LIBUSB_NAME=usb LIBUSB_LIBS= ;; *) LIBUSB_NAME=usb-1.0 LIBUSB_LIBS= ;; esac fi if test x"$LIBUSB_NAME" != x ; then AC_CHECK_LIB($LIBUSB_NAME, libusb_init, [ LIBUSB_LIBS="-l$LIBUSB_NAME $LIBUSB_LIBS" have_libusb=yes ]) AC_MSG_CHECKING([libusb include dir]) usb_incdir_found="no" for _incdir in "" "/usr/include/libusb-1.0" \ "/usr/local/include/libusb-1.0" "/usr/pkg/include/libusb-1.0"; do _libusb_save_cppflags=$CPPFLAGS if test -n "${_incdir}"; then CPPFLAGS="-I${_incdir} ${CPPFLAGS}" fi AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include ]])], [usb_incdir=${_incdir}; usb_incdir_found="yes"], []) CPPFLAGS=${_libusb_save_cppflags} if test "$usb_incdir_found" = "yes"; then break fi done if test "$usb_incdir_found" = "yes"; then AC_MSG_RESULT([${usb_incdir}]) else AC_MSG_RESULT([not found]) usb_incdir="" have_libusb=no if test "$use_ccid_driver" != yes; then use_ccid_driver=no fi LIBUSB_LIBS="" fi if test "$have_libusb" = yes; then AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available]) fi if test x"$usb_incdir" = x; then LIBUSB_CPPFLAGS="" else LIBUSB_CPPFLAGS="-I${usb_incdir}" fi fi AC_SUBST(LIBUSB_LIBS) AC_SUBST(LIBUSB_CPPFLAGS) # # Check whether it is necessary to link against libdl. # (For example to load libpcsclite) # gnupg_dlopen_save_libs="$LIBS" LIBS="" AC_SEARCH_LIBS(dlopen, c dl,,,) DL_LIBS=$LIBS AC_SUBST(DL_LIBS) LIBS="$gnupg_dlopen_save_libs" # Checks for g10 AC_ARG_ENABLE(sqlite, AC_HELP_STRING([--disable-sqlite], [disable the use of SQLITE]), try_sqlite=$enableval, try_sqlite=yes) if test x"$use_tofu" = xyes ; then if test x"$try_sqlite" = xyes ; then PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= $NEED_SQLITE_VERSION], [have_sqlite=yes], [have_sqlite=no]) fi if test "$have_sqlite" = "yes"; then : AC_SUBST([SQLITE3_CFLAGS]) AC_SUBST([SQLITE3_LIBS]) else use_tofu=no tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without SQLite support - TOFU disabled *** *** $tmp]]) fi fi AM_CONDITIONAL(SQLITE3, test "$have_sqlite" = "yes") if test x"$use_tofu" = xyes ; then AC_DEFINE(USE_TOFU, 1, [Enable to build the TOFU code]) fi # Checks for g13 AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs) AC_DEFINE_UNQUOTED(ENCFS, "${ENCFS}", [defines the filename of the encfs program]) AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount) AC_DEFINE_UNQUOTED(FUSERMOUNT, "${FUSERMOUNT}", [defines the filename of the fusermount program]) # Checks for dirmngr # # Checks for symcryptrun: # # libutil has openpty() and login_tty(). AC_CHECK_LIB(util, openpty, [ LIBUTIL_LIBS="$LIBUTIL_LIBS -lutil" AC_DEFINE(HAVE_LIBUTIL,1, [defined if libutil is available]) ]) AC_SUBST(LIBUTIL_LIBS) # shred is used to clean temporary plain text files. AC_PATH_PROG(SHRED, shred, /usr/bin/shred) AC_DEFINE_UNQUOTED(SHRED, "${SHRED}", [defines the filename of the shred program]) # # Check whether the nPth library is available # AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no) if test "$have_npth" = "yes"; then AC_DEFINE(HAVE_NPTH, 1, [Defined if the New Portable Thread Library is available]) AC_DEFINE(USE_NPTH, 1, [Defined if support for nPth is requested and nPth is available]) else AC_MSG_WARN([[ *** *** To support concurrent access for example in gpg-agent and the SCdaemon *** we need the support of the New Portable Threads Library. ***]]) fi # # Enable debugging of nPth # AC_ARG_ENABLE(npth-debug, AC_HELP_STRING([--enable-npth-debug], [build with debug version of npth]), [if test $enableval = yes ; then AC_DEFINE(NPTH_ENABLE_DEBUG,1, [Build with debug version of nPth]) fi]) # # NTBTLS is our TLS library. If it is not available fallback to # GNUTLS. # AC_ARG_ENABLE(ntbtls, AC_HELP_STRING([--disable-ntbtls], [disable the use of NTBTLS as TLS library]), try_ntbtls=$enableval, try_ntbtls=yes) if test x"$try_ntbtls" = xyes ; then AM_PATH_NTBTLS("$NEED_NTBTLS_API:$NEED_NTBTLS_VERSION", [have_ntbtls=yes],[have_ntbtls=no]) fi if test "$have_ntbtls" = yes ; then use_tls_library=ntbtls AC_DEFINE(HTTP_USE_NTBTLS, 1, [Enable NTBTLS support in http.c]) else AC_ARG_ENABLE(gnutls, AC_HELP_STRING([--disable-gnutls], [disable GNUTLS as fallback TLS library]), try_gnutls=$enableval, try_gnutls=yes) if test x"$try_gnutls" = xyes ; then PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= $NEED_GNUTLS_VERSION], [have_gnutls=yes], [have_gnutls=no]) fi if test "$have_gnutls" = "yes"; then AC_SUBST([LIBGNUTLS_CFLAGS]) AC_SUBST([LIBGNUTLS_LIBS]) use_tls_library=gnutls AC_DEFINE(HTTP_USE_GNUTLS, 1, [Enable GNUTLS support in http.c]) else tmp=$(echo "$LIBGNUTLS_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g') AC_MSG_WARN([[ *** *** Building without NTBTLS and GNUTLS - no TLS access to keyservers. *** *** $tmp]]) fi fi # # Allow to set a fixed trust store file for system provided certificates. # AC_ARG_WITH([default-trust-store-file], [AC_HELP_STRING([--with-default-trust-store-file=FILE], [Use FILE as system trust store])], default_trust_store_file="$withval", default_trust_store_file="") if test x"$default_trust_store_file" = xno;then default_trust_store_file="" fi if test x"$default_trust_store_file" != x ; then AC_DEFINE_UNQUOTED([DEFAULT_TRUST_STORE_FILE], ["$default_trust_store_file"], [Use as default system trust store file]) fi AC_MSG_NOTICE([checking for networking options]) # # Must check for network library requirements before doing link tests # for ldap, for example. If ldap libs are static (or dynamic and without # ELF runtime link paths), then link will fail and LDAP support won't # be detected. # AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname, [NETLIBS="-lnsl $NETLIBS"])) AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt, [NETLIBS="-lsocket $NETLIBS"])) # # Check standard resolver functions. # if test "$build_dirmngr" = "yes"; then _dns_save_libs=$LIBS LIBS="" # Find the system resolver which can always be enabled with # the dirmngr option --standard-resolver. # the double underscore thing is a glibc-ism? AC_SEARCH_LIBS(res_query,resolv bind,, AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no)) AC_SEARCH_LIBS(dn_expand,resolv bind,, AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no)) # macOS renames dn_skipname into res_9_dn_skipname in , # and for some reason fools us into believing we don't need # -lresolv even if we do. Since the test program checking for the # symbol does not include , we need to check for the # renamed symbol explicitly. AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,, AC_SEARCH_LIBS(dn_skipname,resolv bind,, AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no))) if test x"$have_resolver" != xno ; then # Make sure that the BIND 4 resolver interface is workable before # enabling any code that calls it. At some point I'll rewrite the # code to use the BIND 8 resolver API. # We might also want to use libdns instead. AC_MSG_CHECKING([whether the resolver is usable]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],have_resolver=yes,have_resolver=no) AC_MSG_RESULT($have_resolver) # This is Apple-specific and somewhat bizarre as they changed the # define in bind 8 for some reason. if test x"$have_resolver" != xyes ; then AC_MSG_CHECKING( [whether I can make the resolver usable with BIND_8_COMPAT]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT #include #include #include #include ]], [[unsigned char answer[PACKETSZ]; res_query("foo.bar",C_IN,T_A,answer,PACKETSZ); dn_skipname(0,0); dn_expand(0,0,0,0,0); ]])],[have_resolver=yes ; need_compat=yes]) AC_MSG_RESULT($have_resolver) fi fi if test x"$have_resolver" = xyes ; then AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.]) DNSLIBS="$DNSLIBS $LIBS" if test x"$need_compat" = xyes ; then AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism]) fi if test "$use_libdns" = yes; then show_tor_support=yes fi elif test "$use_libdns" = yes; then show_tor_support=yes else AC_MSG_WARN([[ *** *** The system's DNS resolver is not usable. *** Dirmngr functionality is limited. ***]]) show_tor_support="${show_tor_support} (no system resolver)" fi if test "$have_w32_system" = yes; then if test "$use_libdns" = yes; then DNSLIBS="$DNSLIBS -liphlpapi" fi fi LIBS=$_dns_save_libs fi AC_SUBST(DNSLIBS) # # Check for LDAP # # Note that running the check changes the variable # gnupg_have_ldap from "n/a" to "no" or "yes". AC_ARG_ENABLE(ldap, AC_HELP_STRING([--disable-ldap],[disable LDAP support]), [if test "$enableval" = "no"; then gnupg_have_ldap=no; fi]) if test "$gnupg_have_ldap" != "no" ; then if test "$build_dirmngr" = "yes" ; then GNUPG_CHECK_LDAP($NETLIBS) AC_CHECK_LIB(lber, ber_free, [ LBER_LIBS="$LBER_LIBS -llber" AC_DEFINE(HAVE_LBER,1, [defined if liblber is available]) have_lber=yes ]) fi fi AC_SUBST(LBER_LIBS) if test "$gnupg_have_ldap" = "no"; then AC_MSG_WARN([[ *** *** Building without LDAP support. *** No CRL access or X.509 certificate search available. ***]]) fi AM_CONDITIONAL(USE_LDAP, [test "$gnupg_have_ldap" = yes]) if test "$gnupg_have_ldap" = yes ; then AC_DEFINE(USE_LDAP,1,[Defined if LDAP is support]) else use_ldapwrapper=no fi if test "$use_ldapwrapper" = yes; then AC_DEFINE(USE_LDAPWRAPPER,1, [Build dirmngr with LDAP wrapper process]) fi AM_CONDITIONAL(USE_LDAPWRAPPER, test "$use_ldapwrapper" = yes) # # Check for sendmail # # This isn't necessarily sendmail itself, but anything that gives a # sendmail-ish interface to the outside world. That includes Exim, # Postfix, etc. Basically, anything that can handle "sendmail -t". AC_ARG_WITH(mailprog, AC_HELP_STRING([--with-mailprog=NAME], [use "NAME -t" for mail transport]), ,with_mailprog=yes) if test x"$with_mailprog" = xyes ; then AC_PATH_PROG(SENDMAIL,sendmail,,$PATH:/usr/sbin:/usr/libexec:/usr/lib) elif test x"$with_mailprog" != xno ; then AC_MSG_CHECKING([for a mail transport program]) AC_SUBST(SENDMAIL,$with_mailprog) AC_MSG_RESULT($with_mailprog) fi +AC_DEFINE_UNQUOTED(NAME_OF_SENDMAIL,"$SENDMAIL", + [Tool with sendmail -t interface]) # # Construct a printable name of the OS # case "${host}" in *-mingw32ce*) PRINTABLE_OS_NAME="W32CE" ;; *-mingw32*) PRINTABLE_OS_NAME="MingW32" ;; *-*-cygwin*) PRINTABLE_OS_NAME="Cygwin" ;; i?86-emx-os2 | i?86-*-os2*emx ) PRINTABLE_OS_NAME="OS/2" ;; i?86-*-msdosdjgpp*) PRINTABLE_OS_NAME="MSDOS/DJGPP" try_dynload=no ;; *-linux*) PRINTABLE_OS_NAME="GNU/Linux" ;; *) PRINTABLE_OS_NAME=`uname -s || echo "Unknown"` ;; esac AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME", [A human readable text with the name of the OS]) # # Checking for iconv # if test "$require_iconv" = yes; then AM_ICONV else LIBICONV= LTLIBICONV= AC_SUBST(LIBICONV) AC_SUBST(LTLIBICONV) fi # # Check for gettext # # This is "GNU gnupg" - The project-id script from gettext # needs this string # AC_MSG_NOTICE([checking for gettext]) AM_PO_SUBDIRS AM_GNU_GETTEXT_VERSION([0.17]) if test "$try_gettext" = yes; then AM_GNU_GETTEXT([external],[need-ngettext]) # gettext requires some extra checks. These really should be part of # the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific # function checks to here. AC_CHECK_FUNCS(strchr) else USE_NLS=no USE_INCLUDED_LIBINTL=no BUILD_INCLUDED_LIBINTL=no POSUB=po AC_SUBST(USE_NLS) AC_SUBST(USE_INCLUDED_LIBINTL) AC_SUBST(BUILD_INCLUDED_LIBINTL) AC_SUBST(POSUB) fi # We use HAVE_LANGINFO_CODESET in a couple of places. AM_LANGINFO_CODESET # Checks required for our use of locales gt_LC_MESSAGES # # SELinux support # if test "$selinux_support" = yes ; then AC_DEFINE(ENABLE_SELINUX_HACKS,1,[Define to enable SELinux support]) fi # # Checks for header files. # AC_MSG_NOTICE([checking for header files]) AC_HEADER_STDC AC_CHECK_HEADERS([string.h unistd.h langinfo.h termio.h locale.h getopt.h \ pty.h utmp.h pwd.h inttypes.h signal.h sys/select.h \ stdint.h signal.h util.h libutil.h termios.h \ ucred.h sys/ucred.h sys/sysmacros.h sys/mkdev.h]) AC_HEADER_TIME # # Checks for typedefs, structures, and compiler characteristics. # AC_MSG_NOTICE([checking for system characteristics]) AC_C_CONST AC_C_INLINE AC_C_VOLATILE AC_TYPE_SIZE_T AC_TYPE_MODE_T AC_TYPE_SIGNAL AC_DECL_SYS_SIGLIST gl_HEADER_SYS_SOCKET gl_TYPE_SOCKLEN_T AC_SEARCH_LIBS([inet_addr], [nsl]) AC_ARG_ENABLE(endian-check, AC_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 GNUPG_CHECK_TYPEDEF(byte, HAVE_BYTE_TYPEDEF) GNUPG_CHECK_TYPEDEF(ushort, HAVE_USHORT_TYPEDEF) GNUPG_CHECK_TYPEDEF(ulong, HAVE_ULONG_TYPEDEF) GNUPG_CHECK_TYPEDEF(u16, HAVE_U16_TYPEDEF) GNUPG_CHECK_TYPEDEF(u32, HAVE_U32_TYPEDEF) AC_CHECK_SIZEOF(unsigned short) AC_CHECK_SIZEOF(unsigned int) AC_CHECK_SIZEOF(unsigned long) AC_CHECK_SIZEOF(unsigned long long) AC_HEADER_TIME AC_CHECK_SIZEOF(time_t,,[[ #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif ]]) GNUPG_TIME_T_UNSIGNED if test "$ac_cv_sizeof_unsigned_short" = "0" \ || test "$ac_cv_sizeof_unsigned_int" = "0" \ || test "$ac_cv_sizeof_unsigned_long" = "0"; then AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]); fi # # Checks for library functions. # AC_MSG_NOTICE([checking for library functions]) AC_CHECK_DECLS(getpagesize) AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK -AC_CHECK_FUNCS([strerror strlwr tcgetattr mmap canonicalize_file_name]) -AC_CHECK_FUNCS([strcasecmp strncasecmp ctermid times gmtime_r strtoull]) -AC_CHECK_FUNCS([setenv unsetenv fcntl ftruncate inet_ntop]) -AC_CHECK_FUNCS([canonicalize_file_name]) -AC_CHECK_FUNCS([gettimeofday getrusage getrlimit setrlimit clock_gettime]) -AC_CHECK_FUNCS([atexit raise getpagesize strftime nl_langinfo setlocale]) -AC_CHECK_FUNCS([waitpid wait4 sigaction sigprocmask pipe getaddrinfo]) -AC_CHECK_FUNCS([ttyname rand ftello fsync stat lstat]) -AC_CHECK_FUNCS([memicmp stpcpy strsep strlwr strtoul memmove stricmp strtol \ - memrchr isascii timegm getrusage setrlimit stat setlocale \ - flockfile funlockfile getpwnam getpwuid \ - getenv inet_pton strpbrk]) +AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \ + fcntl flockfile fsync ftello ftruncate funlockfile \ + getaddrinfo getenv getpagesize getpwnam getpwuid \ + getrlimit getrusage gettimeofday gmtime_r \ + inet_ntop inet_pton isascii lstat \ + memicmp memmove memrchr mmap nl_langinfo pipe \ + raise rand setenv setlocale setrlimit sigaction \ + sigprocmask stat stpcpy strcasecmp strerror strftime \ + stricmp strlwr strncasecmp strpbrk strsep \ + strtol strtoul strtoull tcgetattr timegm times \ + ttyname unsetenv wait4 waitpid ]) # On some systems (e.g. Solaris) nanosleep requires linking to librl. # Given that we use nanosleep only as an optimization over a select # based wait function we want it only if it is available in libc. _save_libs="$LIBS" AC_SEARCH_LIBS([nanosleep], [], [AC_DEFINE(HAVE_NANOSLEEP,1, [Define to 1 if you have the `nanosleep' function in libc.])]) LIBS="$_save_libs" # See whether libc supports the Linux inotify interface case "${host}" in *-*-linux*) AC_CHECK_FUNCS([inotify_init]) ;; esac if test "$have_android_system" = yes; then # On Android ttyname is a stub but prints an error message. AC_DEFINE(HAVE_BROKEN_TTYNAME,1, [Defined if ttyname does not work properly]) fi AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) # Dirmngr requires mmap on Unix systems. if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then AC_MSG_ERROR([[Sorry, the current implementation requires mmap.]]) fi # # Check for the getsockopt SO_PEERCRED, etc. # AC_CHECK_MEMBERS([struct ucred.pid, struct ucred.cr_pid, struct sockpeercred.pid], [], [], [#include #include ]) # (Open)Solaris AC_CHECK_FUNCS([getpeerucred]) # # W32 specific test # GNUPG_FUNC_MKDIR_TAKES_ONE_ARG # # Sanity check regex. Tests adapted from mutt. # AC_MSG_CHECKING([whether regular expression support is requested]) AC_ARG_ENABLE(regex, AC_HELP_STRING([--disable-regex], [do not handle regular expressions in trust signatures]), use_regex=$enableval, use_regex=yes) AC_MSG_RESULT($use_regex) if test "$use_regex" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(regex, AC_HELP_STRING([--with-regex=DIR],[look for regex in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Does the system have regex functions at all? AC_SEARCH_LIBS([regcomp], [regex]) AC_CHECK_FUNC(regcomp, gnupg_cv_have_regex=yes, gnupg_cv_have_regex=no) if test $gnupg_cv_have_regex = no; then use_regex=no else if test x"$cross_compiling" = xyes; then AC_MSG_WARN([cross compiling; assuming regexp libray is not broken]) else AC_CACHE_CHECK([whether your system's regexp library is broken], [gnupg_cv_regex_broken], AC_TRY_RUN([ #include #include main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }], gnupg_cv_regex_broken=no, gnupg_cv_regex_broken=yes, gnupg_cv_regex_broken=yes)) if test $gnupg_cv_regex_broken = yes; then AC_MSG_WARN([your regex is broken - disabling regex use]) use_regex=no fi fi fi CPPFLAGS="${_cppflags}" LDFLAGS="${_ldflags}" fi if test "$use_regex" != yes ; then AC_DEFINE(DISABLE_REGEX,1, [Define to disable regular expression support]) fi AM_CONDITIONAL(DISABLE_REGEX, test x"$use_regex" != xyes) # # Do we have zlib? Must do it here because Solaris failed # when compiling a conftest (due to the "-lz" from LIBS). # Note that we combine zlib and bzlib2 in ZLIBS. # if test "$use_zip" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(zlib, [ --with-zlib=DIR use libz in DIR],[ if test -d "$withval"; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ]) AC_CHECK_HEADER(zlib.h, AC_CHECK_LIB(z, deflateInit2_, [ ZLIBS="-lz" AC_DEFINE(HAVE_ZIP,1, [Defined if ZIP and ZLIB are supported]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}) fi # # Check whether we can support bzip2 # if test "$use_bzip2" = yes ; then _cppflags="${CPPFLAGS}" _ldflags="${LDFLAGS}" AC_ARG_WITH(bzip2, AC_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]), [ if test -d "$withval" ; then CPPFLAGS="${CPPFLAGS} -I$withval/include" LDFLAGS="${LDFLAGS} -L$withval/lib" fi ],withval="") # Checking alongside stdio.h as an early version of bzip2 (1.0) # required stdio.h to be included before bzlib.h, and Solaris 9 is # woefully out of date. if test "$withval" != no ; then AC_CHECK_HEADER(bzlib.h, AC_CHECK_LIB(bz2,BZ2_bzCompressInit, [ have_bz2=yes ZLIBS="$ZLIBS -lbz2" AC_DEFINE(HAVE_BZIP2,1, [Defined if the bz2 compression library is available]) ], CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}), CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include ]) fi fi AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes") AC_SUBST(ZLIBS) # Check for readline support GNUPG_CHECK_READLINE if test "$development_version" = yes; then AC_DEFINE(IS_DEVELOPMENT_VERSION,1, [Defined if this is not a regular release]) fi if test "$USE_MAINTAINER_MODE" = "yes"; then AC_DEFINE(MAINTAINER_MODE,1, [Defined if this build is in maintainer mode]) fi AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes) GNUPG_CHECK_GNUMAKE # Add some extra libs here so that previous tests don't fail for # mysterious reasons - the final link step should bail out. # W32SOCKLIBS is also defined so that if can be used for tools not # requiring any network stuff but linking to code in libcommon which # tracks in winsock stuff (e.g. init_common_subsystems). if test "$have_w32_system" = yes; then if test "$have_w32ce_system" = yes; then W32SOCKLIBS="-lws2" else W32SOCKLIBS="-lws2_32" fi NETLIBS="${NETLIBS} ${W32SOCKLIBS}" fi AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) # # Setup gcc specific options # USE_C99_CFLAGS= AC_MSG_NOTICE([checking for cc features]) if test "$GCC" = yes; then mycflags= mycflags_save=$CFLAGS # Check whether gcc does not emit a diagnositc 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([[ #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6 ) #kickerror #endif]],[])],[_gcc_silent_wno=yes],[_gcc_silent_wno=no]) AC_MSG_RESULT($_gcc_silent_wno) # Note that it is okay to use CFLAGS here because these are just # warning options and the user should have a chance of overriding # them. if test "$USE_MAINTAINER_MODE" = "yes"; then mycflags="$mycflags -O3 -Wall -Wcast-align -Wshadow -Wstrict-prototypes" mycflags="$mycflags -Wformat -Wno-format-y2k -Wformat-security" if test x"$_gcc_silent_wno" = xyes ; then _gcc_wopt=yes else AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers]) CFLAGS="-Wno-missing-field-initializers" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_wopt=yes],[_gcc_wopt=no]) AC_MSG_RESULT($_gcc_wopt) fi if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -W -Wno-sign-compare -Wno-format-zero-length" mycflags="$mycflags -Wno-missing-field-initializers" fi AC_MSG_CHECKING([if gcc supports -Wdeclaration-after-statement]) CFLAGS="-Wdeclaration-after-statement" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wdeclaration-after-statement" fi AC_MSG_CHECKING([if gcc supports -Wlogical-op]) CFLAGS="-Wlogical-op -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wlogical-op" fi AC_MSG_CHECKING([if gcc supports -Wvla]) CFLAGS="-Wvla" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no) AC_MSG_RESULT($_gcc_wopt) if test x"$_gcc_wopt" = xyes ; then mycflags="$mycflags -Wvla" fi else mycflags="$mycflags -Wall" fi if test x"$_gcc_silent_wno" = xyes ; then _gcc_psign=yes else AC_MSG_CHECKING([if gcc supports -Wno-pointer-sign]) CFLAGS="-Wno-pointer-sign" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [_gcc_psign=yes],[_gcc_psign=no]) AC_MSG_RESULT($_gcc_psign) fi if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wno-pointer-sign" fi AC_MSG_CHECKING([if gcc supports -Wpointer-arith]) CFLAGS="-Wpointer-arith" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_psign=yes,_gcc_psign=no) AC_MSG_RESULT($_gcc_psign) if test x"$_gcc_psign" = xyes ; then mycflags="$mycflags -Wpointer-arith" fi CFLAGS="$mycflags $mycflags_save" if test "$use_libdns" = yes; then # dirmngr/dns.{c,h} require C99 and GNU extensions. */ USE_C99_CFLAGS="-std=gnu99" fi fi AC_SUBST(USE_C99_CFLAGS) # # This is handy for debugging so the compiler doesn't rearrange # things and eliminate variables. # AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable compiler optimization]), [if test $enableval = no ; then CFLAGS=`echo $CFLAGS | sed s/-O[[1-9]]\ /-O0\ /g` fi]) # # log_debug has certain requirements which might hamper portability. # Thus we use an option to enable it. # AC_MSG_CHECKING([whether to enable log_clock]) AC_ARG_ENABLE(log_clock, AC_HELP_STRING([--enable-log-clock], [enable log_clock timestamps]), enable_log_clock=$enableval, enable_log_clock=no) AC_MSG_RESULT($enable_log_clock) if test "$enable_log_clock" = yes ; then AC_DEFINE(ENABLE_LOG_CLOCK,1,[Defined to use log_clock timestamps]) fi # Add -Werror to CFLAGS. This hack can be used to avoid problems with # misbehaving autoconf tests in case the user supplied -Werror. # AC_ARG_ENABLE(werror, AC_HELP_STRING([--enable-werror], [append -Werror to CFLAGS]), [if test $enableval = yes ; then CFLAGS="$CFLAGS -Werror" fi]) # # Configure option --enable-all-tests # AC_MSG_CHECKING([whether "make check" shall run all tests]) AC_ARG_ENABLE(all-tests, AC_HELP_STRING([--enable-all-tests], [let "make check" run all tests]), run_all_tests=$enableval, run_all_tests=no) AC_MSG_RESULT($run_all_tests) if test "$run_all_tests" = "yes"; then AC_DEFINE(RUN_ALL_TESTS,1, [Defined if "make check" shall run all tests]) fi # # We do not want support for the GNUPG_BUILDDIR environment variable # in a released version. However, our regression tests suite requires # this and thus we build with support for it during "make distcheck". # This configure option implements this along with the top Makefile's # AM_DISTCHECK_CONFIGURE_FLAGS. # gnupg_builddir_envvar=no AC_ARG_ENABLE(gnupg-builddir-envvar,, gnupg_builddir_envvar=$enableval) if test x"$gnupg_builddir_envvar" = x"yes"; then AC_DEFINE(ENABLE_GNUPG_BUILDDIR_ENVVAR, 1, [This is only used with "make distcheck"]) fi # # To avoid problems with systemd cleaning up the /run/user directory, # this option will make GnuPG try to use /run/gnupg/user as socket dir # before /run/user # AC_ARG_ENABLE(run-gnupg-user-socket, AC_HELP_STRING([--enable-run-gnupg-user-socket], [try /run/gnupg/user for sockets prior to /run/user]), use_run_gnupg_user_socket=$enableval) if test x"$use_run_gnupg_user_socket" = x"yes"; then AC_DEFINE(USE_RUN_GNUPG_USER_SOCKET, 1, [If defined try /run/gnupg/user before /run/user]) fi # # Decide what to build # build_scdaemon_extra="" if test "$build_scdaemon" = "yes"; then if test $have_libusb = no; then build_scdaemon_extra="without internal CCID driver" fi if test -n "$build_scdaemon_extra"; then build_scdaemon_extra="(${build_scdaemon_extra})" fi fi # # Set variables for use by automake makefiles. # AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes") AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes") AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes) AM_CONDITIONAL(NO_TRUST_MODELS, test "$use_trust_models" = no) AM_CONDITIONAL(USE_TOFU, test "$use_tofu" = yes) # # Set some defines for use gpgconf. # if test "$build_gpg" = yes ; then AC_DEFINE(BUILD_WITH_GPG,1,[Defined if GPG is to be build]) fi if test "$build_gpgsm" = yes ; then AC_DEFINE(BUILD_WITH_GPGSM,1,[Defined if GPGSM is to be build]) fi if test "$build_agent" = yes ; then AC_DEFINE(BUILD_WITH_AGENT,1,[Defined if GPG-AGENT is to be build]) fi if test "$build_scdaemon" = yes ; then AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build]) fi if test "$build_dirmngr" = yes ; then AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if DIRMNGR is to be build]) fi if test "$build_g13" = yes ; then AC_DEFINE(BUILD_WITH_G13,1,[Defined if G13 is to be build]) fi # # Define Name strings # AC_DEFINE_UNQUOTED(GNUPG_NAME, "GnuPG", [The name of the project]) AC_DEFINE_UNQUOTED(GPG_NAME, "gpg", [The name of the OpenPGP tool]) AC_DEFINE_UNQUOTED(GPG_DISP_NAME, "GnuPG", [The displayed name of gpg]) AC_DEFINE_UNQUOTED(GPGSM_NAME, "gpgsm", [The name of the S/MIME tool]) AC_DEFINE_UNQUOTED(GPGSM_DISP_NAME, "GPGSM", [The displayed name of gpgsm]) AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent]) AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent", [The displayed name of gpg-agent]) AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr]) AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr", [The displayed name of dirmngr]) AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool]) AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13]) AC_DEFINE_UNQUOTED(GPGCONF_NAME, "gpgconf", [The name of the gpgconf tool]) AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf", [The displayed name of gpgconf]) AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool]) AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent", [The name of the agent socket]) AC_DEFINE_UNQUOTED(GPG_AGENT_EXTRA_SOCK_NAME, "S.gpg-agent.extra", [The name of the agent socket for remote access]) AC_DEFINE_UNQUOTED(GPG_AGENT_BROWSER_SOCK_NAME, "S.gpg-agent.browser", [The name of the agent socket for browsers]) AC_DEFINE_UNQUOTED(GPG_AGENT_SSH_SOCK_NAME, "S.gpg-agent.ssh", [The name of the agent socket for ssh]) AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, "hkps://hkps.pool.sks-keyservers.net", [The default keyserver for dirmngr to use, if none is explicitly given]) AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix]) if test "$have_w32_system" = yes; then AC_DEFINE_UNQUOTED(GNUPG_REGISTRY_DIR, "Software\\\\GNU\\\\GnuPG", [The directory part of the W32 registry keys]) fi # # Provide information about the build. # BUILD_REVISION="mym4_revision" AC_SUBST(BUILD_REVISION) AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION", [GIT commit id revision used to build this package]) changequote(,)dnl BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'` changequote([,])dnl BUILD_VERSION="${BUILD_VERSION}mym4_revision_dec" BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,` AC_SUBST(BUILD_VERSION) AC_SUBST(BUILD_FILEVERSION) AC_ARG_ENABLE([build-timestamp], AC_HELP_STRING([--enable-build-timestamp], [set an explicit build timestamp for reproducibility. (default is the current time in ISO-8601 format)]), [if test "$enableval" = "yes"; then BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date` else BUILD_TIMESTAMP="$enableval" fi BUILD_HOSTNAME="$ac_hostname"], [BUILD_TIMESTAMP="" BUILD_HOSTNAME=""]) AC_SUBST(BUILD_TIMESTAMP) AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP", [The time this package was configured for a build]) AC_SUBST(BUILD_HOSTNAME) # # Print errors here so that they are visible all # together and the user can acquire them all together. # die=no if test "$have_gpg_error" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgpg-error to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libgpg-error *** (at least version $NEED_GPG_ERROR_VERSION is required.) ***]]) fi if test "$have_libgcrypt" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libgcrypt to build this program. ** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libgcrypt/ *** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.) ***]]) fi if test "$have_libassuan" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libassuan to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libassuan/ *** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required). ***]]) fi if test "$have_ksba" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** You need libksba to build this program. *** This library is for example available at *** https://gnupg.org/ftp/gcrypt/libksba/ *** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required). ***]]) fi if test "$gnupg_have_ldap" = yes; then if test "$have_w32ce_system" = yes; then AC_MSG_NOTICE([[ *** Note that CeGCC might be broken, a package fixing this is: *** http://files.kolab.org/local/windows-ce/ *** source/wldap32_0.1-mingw32ce.orig.tar.gz *** binary/wldap32-ce-arm-dev_0.1-1_all.deb ***]]) fi fi if test "$have_npth" = "no"; then die=yes AC_MSG_NOTICE([[ *** *** It is now required to build with support for the *** New Portable Threads Library (nPth). Please install this *** library first. The library is for example available at *** https://gnupg.org/ftp/gcrypt/npth/ *** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required). ***]]) fi if test "$require_iconv" = yes; then if test "$am_func_iconv" != yes; then die=yes AC_MSG_NOTICE([[ *** *** The system does not provide a working iconv function. Please *** install a suitable library; for example GNU Libiconv which is *** available at: *** https://ftp.gnu.org/gnu/libiconv/ ***]]) fi fi if test "$use_ccid_driver" = yes; then if test "$have_libusb" != yes; then die=yes AC_MSG_NOTICE([[ *** *** You need libusb to build the internal ccid driver. Please *** install a libusb suitable for your system. ***]]) fi fi if test "$die" = "yes"; then AC_MSG_ERROR([[ *** *** Required libraries not found. Please consult the above messages *** and install them before running configure again. ***]]) fi AC_CONFIG_FILES([ m4/Makefile Makefile po/Makefile.in common/Makefile common/w32info-rc.h kbx/Makefile g10/Makefile sm/Makefile agent/Makefile scd/Makefile g13/Makefile dirmngr/Makefile tools/gpg-zip tools/Makefile doc/Makefile tests/Makefile tests/gpgscm/Makefile tests/openpgp/Makefile tests/migrations/Makefile tests/gpgsm/Makefile tests/gpgme/Makefile tests/pkits/Makefile g10/gpg.w32-manifest ]) AC_OUTPUT echo " GnuPG v${VERSION} has been configured as follows: Revision: mym4_revision (mym4_revision_dec) Platform: $PRINTABLE_OS_NAME ($host) OpenPGP: $build_gpg S/MIME: $build_gpgsm Agent: $build_agent Smartcard: $build_scdaemon $build_scdaemon_extra G13: $build_g13 Dirmngr: $build_dirmngr Gpgtar: $build_gpgtar WKS tools: $build_wks_tools Protect tool: $show_gnupg_protect_tool_pgm LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start Readline support: $gnupg_cv_have_readline LDAP support: $gnupg_have_ldap TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support " if test x"$use_regex" != xyes ; then echo " Warning: No regular expression support available. OpenPGP trust signatures won't work. gpg-check-pattern will not be built. " fi if test "x${gpg_config_script_warn}" != x; then cat <. */ #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # ifdef HAVE_WINSOCK2_H # include # endif # include # include #else # if HAVE_SYSTEM_RESOLVER # include # include # include # endif # include #endif #ifdef HAVE_STAT # include #endif #include #include /* William Ahern's DNS library, included as a source copy. */ #ifdef USE_LIBDNS # include "dns.h" #endif /* dns.c has a dns_p_free but it is not exported. We use our own * wrapper here so that we do not accidentally use xfree which would * be wrong for dns.c allocated data. */ #define dns_free(a) free ((a)) #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ # undef USE_NPTH #endif #ifdef USE_NPTH # include #endif #include "./dirmngr-err.h" #include "../common/util.h" #include "../common/host2net.h" #include "dns-stuff.h" #ifdef USE_NPTH # define my_unprotect() npth_unprotect () # define my_protect() npth_protect () #else # define my_unprotect() do { } while(0) # define my_protect() do { } while(0) #endif /* We allow the use of 0 instead of AF_UNSPEC - check this assumption. */ #if AF_UNSPEC != 0 # error AF_UNSPEC does not have the value 0 #endif /* Windows does not support the AI_ADDRCONFIG flag - use zero instead. */ #ifndef AI_ADDRCONFIG # define AI_ADDRCONFIG 0 #endif /* Not every installation has gotten around to supporting SRVs or CERTs yet... */ #ifndef T_SRV #define T_SRV 33 #endif #undef T_CERT #define T_CERT 37 /* The standard SOCKS and TOR ports. */ #define SOCKS_PORT 1080 #define TOR_PORT 9050 #define TOR_PORT2 9150 /* (Used by the Tor browser) */ /* The default nameserver used in Tor mode. */ #define DEFAULT_NAMESERVER "8.8.8.8" /* The default timeout in seconds for libdns requests. */ #define DEFAULT_TIMEOUT 30 #define RESOLV_CONF_NAME "/etc/resolv.conf" /* Two flags to enable verbose and debug mode. */ static int opt_verbose; static int opt_debug; /* The timeout in seconds for libdns requests. */ static int opt_timeout; /* The flag to disable IPv4 access - right now this only skips * returned A records. */ static int opt_disable_ipv4; /* The flag to disable IPv6 access - right now this only skips * returned AAAA records. */ static int opt_disable_ipv6; /* If set force the use of the standard resolver. */ static int standard_resolver; /* If set use recursive resolver when available. */ static int recursive_resolver; /* If set Tor mode shall be used. */ static int tor_mode; /* A string with the nameserver IP address used with Tor. (40 should be sufficient for v6 but we add some extra for a scope.) */ static char tor_nameserver[40+20]; /* Two strings to hold the credentials presented to Tor. */ static char tor_socks_user[30]; static char tor_socks_password[20]; #ifdef USE_LIBDNS /* Libdns gobal data. */ struct libdns_s { struct dns_resolv_conf *resolv_conf; struct dns_hosts *hosts; struct dns_hints *hints; struct sockaddr_storage socks_host; } libdns; /* If this flag is set, libdns shall be reinited for the next use. */ static int libdns_reinit_pending; /* The Tor port to be used. */ static int libdns_tor_port; #endif /*USE_LIBDNS*/ /* Calling this function with YES set to True forces the use of the * standard resolver even if dirmngr has been built with support for * an alternative resolver. */ void enable_standard_resolver (int yes) { standard_resolver = yes; } /* Return true if the standard resolver is used. */ int standard_resolver_p (void) { return standard_resolver; } /* Calling this function with YES switches libdns into recursive mode. * It has no effect on the standard resolver. */ void enable_recursive_resolver (int yes) { recursive_resolver = yes; #ifdef USE_LIBDNS libdns_reinit_pending = 1; #endif } /* Return true iff the recursive resolver is used. */ int recursive_resolver_p (void) { #if USE_LIBDNS return !standard_resolver && recursive_resolver; #else return 0; #endif } /* Puts this module eternally into Tor mode. When called agained with * NEW_CIRCUIT request a new TOR circuit for the next DNS query. */ void enable_dns_tormode (int new_circuit) { if (!*tor_socks_user || new_circuit) { static unsigned int counter; gpgrt_snprintf (tor_socks_user, sizeof tor_socks_user, "dirmngr-%lu", (unsigned long)getpid ()); gpgrt_snprintf (tor_socks_password, sizeof tor_socks_password, "p%u", counter); counter++; } tor_mode = 1; } /* Disable tor mode. */ void disable_dns_tormode (void) { tor_mode = 0; } /* Set verbosity and debug mode for this module. */ void set_dns_verbose (int verbose, int debug) { opt_verbose = verbose; opt_debug = debug; } /* Set the Disable-IPv4 flag so that the name resolver does not return * A addresses. */ void set_dns_disable_ipv4 (int yes) { opt_disable_ipv4 = !!yes; } /* Set the Disable-IPv6 flag so that the name resolver does not return * AAAA addresses. */ void set_dns_disable_ipv6 (int yes) { opt_disable_ipv6 = !!yes; } /* Set the timeout for libdns requests to SECONDS. A value of 0 sets * the default timeout and values are capped at 10 minutes. */ void set_dns_timeout (int seconds) { if (!seconds) seconds = DEFAULT_TIMEOUT; else if (seconds < 1) seconds = 1; else if (seconds > 600) seconds = 600; opt_timeout = seconds; } /* Change the default IP address of the nameserver to IPADDR. The address needs to be a numerical IP address and will be used for the next DNS query. Note that this is only used in Tor mode. */ void set_dns_nameserver (const char *ipaddr) { strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER, sizeof tor_nameserver -1); tor_nameserver[sizeof tor_nameserver -1] = 0; #ifdef USE_LIBDNS libdns_reinit_pending = 1; libdns_tor_port = 0; /* Start again with the default port. */ #endif } /* Free an addressinfo linked list as returned by resolve_dns_name. */ void free_dns_addrinfo (dns_addrinfo_t ai) { while (ai) { dns_addrinfo_t next = ai->next; xfree (ai); ai = next; } } #ifndef HAVE_W32_SYSTEM /* Return H_ERRNO mapped to a gpg-error code. Will never return 0. */ static gpg_error_t get_h_errno_as_gpg_error (void) { gpg_err_code_t ec; switch (h_errno) { case HOST_NOT_FOUND: ec = GPG_ERR_NO_NAME; break; case TRY_AGAIN: ec = GPG_ERR_TRY_LATER; break; case NO_RECOVERY: ec = GPG_ERR_SERVER_FAILED; break; case NO_DATA: ec = GPG_ERR_NO_DATA; break; default: ec = GPG_ERR_UNKNOWN_ERRNO; break; } return gpg_error (ec); } #endif /*!HAVE_W32_SYSTEM*/ static gpg_error_t map_eai_to_gpg_error (int ec) { gpg_error_t err; switch (ec) { case EAI_AGAIN: err = gpg_error (GPG_ERR_EAGAIN); break; case EAI_BADFLAGS: err = gpg_error (GPG_ERR_INV_FLAG); break; case EAI_FAIL: err = gpg_error (GPG_ERR_SERVER_FAILED); break; case EAI_MEMORY: err = gpg_error (GPG_ERR_ENOMEM); break; #ifdef EAI_NODATA case EAI_NODATA: err = gpg_error (GPG_ERR_NO_DATA); break; #endif case EAI_NONAME: err = gpg_error (GPG_ERR_NO_NAME); break; case EAI_SERVICE: err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; case EAI_FAMILY: err = gpg_error (GPG_ERR_EAFNOSUPPORT); break; case EAI_SOCKTYPE: err = gpg_error (GPG_ERR_ESOCKTNOSUPPORT); break; #ifndef HAVE_W32_SYSTEM # ifdef EAI_ADDRFAMILY case EAI_ADDRFAMILY:err = gpg_error (GPG_ERR_EADDRNOTAVAIL); break; # endif case EAI_SYSTEM: err = gpg_error_from_syserror (); break; #endif default: err = gpg_error (GPG_ERR_UNKNOWN_ERRNO); break; } return err; } #ifdef USE_LIBDNS static gpg_error_t libdns_error_to_gpg_error (int serr) { gpg_err_code_t ec; switch (serr) { case 0: ec = 0; break; case DNS_ENOBUFS: ec = GPG_ERR_BUFFER_TOO_SHORT; break; case DNS_EILLEGAL: ec = GPG_ERR_INV_OBJ; break; case DNS_EORDER: ec = GPG_ERR_INV_ORDER; break; case DNS_ESECTION: ec = GPG_ERR_DNS_SECTION; break; case DNS_EUNKNOWN: ec = GPG_ERR_DNS_UNKNOWN; break; case DNS_EADDRESS: ec = GPG_ERR_DNS_ADDRESS; break; case DNS_ENOQUERY: ec = GPG_ERR_DNS_NO_QUERY; break; case DNS_ENOANSWER:ec = GPG_ERR_DNS_NO_ANSWER; break; case DNS_EFETCHED: ec = GPG_ERR_ALREADY_FETCHED; break; case DNS_ESERVICE: ec = GPG_ERR_NOT_SUPPORTED; break; case DNS_ENONAME: ec = GPG_ERR_NO_NAME; break; case DNS_EFAIL: ec = GPG_ERR_SERVER_FAILED; break; case DNS_ECONNFIN: ec = GPG_ERR_DNS_CLOSED; break; case DNS_EVERIFY: ec = GPG_ERR_DNS_VERIFY; break; default: if (serr >= 0) ec = gpg_err_code_from_errno (serr); else ec = GPG_ERR_DNS_UNKNOWN; break; } return gpg_error (ec); } #endif /*USE_LIBDNS*/ /* Return true if resolve.conf changed since it was last loaded. */ #ifdef USE_LIBDNS static int resolv_conf_changed_p (void) { #if defined(HAVE_W32_SYSTEM) || !defined(HAVE_STAT) return 0; #else static time_t last_mtime; const char *fname = RESOLV_CONF_NAME; struct stat statbuf; int changed = 0; if (stat (fname, &statbuf)) { log_error ("stat'ing '%s' failed: %s\n", fname, gpg_strerror (gpg_error_from_syserror ())); last_mtime = 1; /* Force a "changed" result the next time stat * works. */ } else if (!last_mtime) last_mtime = statbuf.st_mtime; else if (last_mtime != statbuf.st_mtime) { changed = 1; last_mtime = statbuf.st_mtime; } return changed; #endif } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Initialize libdns. Returns 0 on success; prints a diagnostic and * returns an error code on failure. */ static gpg_error_t libdns_init (void) { gpg_error_t err; struct libdns_s ld; int derr; char *cfgstr = NULL; if (libdns.resolv_conf) return 0; /* Already initialized. */ memset (&ld, 0, sizeof ld); ld.resolv_conf = dns_resconf_open (&derr); if (!ld.resolv_conf) { err = libdns_error_to_gpg_error (derr); log_error ("failed to allocate DNS resconf object: %s\n", gpg_strerror (err)); goto leave; } if (tor_mode) { if (!*tor_nameserver) set_dns_nameserver (NULL); if (!libdns_tor_port) libdns_tor_port = TOR_PORT; cfgstr = xtryasprintf ("[%s]:53", tor_nameserver); if (!cfgstr) err = gpg_error_from_syserror (); else err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.resolv_conf->nameserver[0], cfgstr)); if (err) log_error ("failed to set nameserver '%s': %s\n", cfgstr, gpg_strerror (err)); if (err) goto leave; ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS; xfree (cfgstr); cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port); if (!cfgstr) err = gpg_error_from_syserror (); else err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.socks_host, cfgstr)); if (err) { log_error ("failed to set socks server '%s': %s\n", cfgstr, gpg_strerror (err)); goto leave; } } else { #ifdef HAVE_W32_SYSTEM ULONG ninfo_len; PFIXED_INFO ninfo; PIP_ADDR_STRING pip; int idx; ninfo_len = 2048; ninfo = xtrymalloc (ninfo_len); if (!ninfo) { err = gpg_error_from_syserror (); goto leave; } if (GetNetworkParams (ninfo, &ninfo_len)) { log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1)); err = gpg_error (GPG_ERR_GENERAL); xfree (ninfo); goto leave; } for (idx=0, pip = &(ninfo->DnsServerList); pip && idx < DIM (ld.resolv_conf->nameserver); pip = pip->Next) { if (opt_debug) log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String); err = libdns_error_to_gpg_error (dns_resconf_pton (&ld.resolv_conf->nameserver[idx], pip->IpAddress.String)); if (err) log_error ("failed to set nameserver[%d] '%s': %s\n", idx, pip->IpAddress.String, gpg_strerror (err)); else idx++; } xfree (ninfo); #else /* Unix */ const char *fname; fname = RESOLV_CONF_NAME; resolv_conf_changed_p (); /* Reset timestamp. */ err = libdns_error_to_gpg_error (dns_resconf_loadpath (ld.resolv_conf, fname)); if (err) { log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); goto leave; } fname = "/etc/nsswitch.conf"; err = libdns_error_to_gpg_error (dns_nssconf_loadpath (ld.resolv_conf, fname)); if (err) { /* This is not a fatal error: nsswitch.conf is not used on * all systems; assume classic behavior instead. */ if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); if (opt_debug) log_debug ("dns: fallback resolution order, files then DNS\n"); ld.resolv_conf->lookup[0] = 'f'; ld.resolv_conf->lookup[1] = 'b'; ld.resolv_conf->lookup[2] = '\0'; err = GPG_ERR_NO_ERROR; } else if (!strchr (ld.resolv_conf->lookup, 'b')) { /* No DNS resolution type found in the list. This might be * due to systemd based systems which allow for custom * keywords which are not known to us and thus we do not * know whether DNS is wanted or not. Because DNS is * important for our infrastructure, we forcefully append * DNS to the end of the list. */ if (strlen (ld.resolv_conf->lookup)+2 < sizeof ld.resolv_conf->lookup) { if (opt_debug) log_debug ("dns: appending DNS to resolution order\n"); strcat (ld.resolv_conf->lookup, "b"); } else log_error ("failed to append DNS to resolution order\n"); } #endif /* Unix */ } ld.hosts = dns_hosts_open (&derr); if (!ld.hosts) { err = libdns_error_to_gpg_error (derr); log_error ("failed to initialize hosts file: %s\n", gpg_strerror (err)); goto leave; } { #if HAVE_W32_SYSTEM char *hosts_path = xtryasprintf ("%s\\System32\\drivers\\etc\\hosts", getenv ("SystemRoot")); if (! hosts_path) { err = gpg_error_from_syserror (); goto leave; } derr = dns_hosts_loadpath (ld.hosts, hosts_path); xfree (hosts_path); #else derr = dns_hosts_loadpath (ld.hosts, "/etc/hosts"); #endif if (derr) { err = libdns_error_to_gpg_error (derr); log_error ("failed to load hosts file: %s\n", gpg_strerror (err)); err = 0; /* Do not bail out - having no /etc/hosts is legal. */ } } + ld.resolv_conf->options.recurse = recursive_resolver_p (); + /* dns_hints_local for stub mode, dns_hints_root for recursive. */ ld.hints = (recursive_resolver ? dns_hints_root (ld.resolv_conf, &derr) : dns_hints_local (ld.resolv_conf, &derr)); if (!ld.hints) { err = libdns_error_to_gpg_error (derr); log_error ("failed to load DNS hints: %s\n", gpg_strerror (err)); goto leave; } /* All fine. Make the data global. */ libdns = ld; if (opt_debug) log_debug ("dns: libdns initialized%s\n", tor_mode?" (tor mode)":""); leave: xfree (cfgstr); return err; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Deinitialize libdns. */ static void libdns_deinit (void) { struct libdns_s ld; if (!libdns.resolv_conf) return; /* Not initialized. */ ld = libdns; memset (&libdns, 0, sizeof libdns); dns_hints_close (ld.hints); dns_hosts_close (ld.hosts); dns_resconf_close (ld.resolv_conf); } #endif /*USE_LIBDNS*/ /* SIGHUP action handler for this module. With FORCE set objects are * all immediately released. */ void reload_dns_stuff (int force) { #ifdef USE_LIBDNS if (force) { libdns_deinit (); libdns_reinit_pending = 0; } else { libdns_reinit_pending = 1; libdns_tor_port = 0; /* Start again with the default port. */ } #else (void)force; #endif } #ifdef USE_LIBDNS /* * Initialize libdns if needed and open a dns_resolver context. * Returns 0 on success and stores the new context at R_RES. On * failure an error code is returned and NULL stored at R_RES. */ static gpg_error_t libdns_res_open (struct dns_resolver **r_res) { gpg_error_t err; struct dns_resolver *res; int derr; *r_res = NULL; /* Force a reload if resolv.conf has changed. */ if (resolv_conf_changed_p ()) { if (opt_debug) log_debug ("dns: resolv.conf changed - forcing reload\n"); libdns_reinit_pending = 1; } if (libdns_reinit_pending) { libdns_reinit_pending = 0; libdns_deinit (); } err = libdns_init (); if (err) return err; if (!opt_timeout) set_dns_timeout (0); res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL, dns_opts (.socks_host = &libdns.socks_host, .socks_user = tor_socks_user, .socks_password = tor_socks_password ), &derr); if (!res) return libdns_error_to_gpg_error (derr); *r_res = res; return 0; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Helper to test whether we need to try again after having switched * the Tor port. */ static int libdns_switch_port_p (gpg_error_t err) { if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED && libdns_tor_port == TOR_PORT) { /* Switch port and try again. */ if (opt_debug) log_debug ("dns: switching from SOCKS port %d to %d\n", TOR_PORT, TOR_PORT2); libdns_tor_port = TOR_PORT2; libdns_reinit_pending = 1; return 1; } return 0; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Wrapper around dns_res_submit. */ static gpg_error_t libdns_res_submit (struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass) { return libdns_error_to_gpg_error (dns_res_submit (res, qname, qtype, qclass)); } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS /* Standard event handling loop. */ gpg_error_t libdns_res_wait (struct dns_resolver *res) { gpg_error_t err; while ((err = libdns_error_to_gpg_error (dns_res_check (res))) && gpg_err_code (err) == GPG_ERR_EAGAIN) { if (dns_res_elapsed (res) > opt_timeout) { err = gpg_error (GPG_ERR_DNS_TIMEOUT); break; } my_unprotect (); dns_res_poll (res, 1); my_protect (); } return err; } #endif /*USE_LIBDNS*/ #ifdef USE_LIBDNS static gpg_error_t resolve_name_libdns (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_dai, char **r_canonname) { gpg_error_t err; dns_addrinfo_t daihead = NULL; dns_addrinfo_t dai; struct dns_resolver *res = NULL; struct dns_addrinfo *ai = NULL; struct addrinfo hints; struct addrinfo *ent; char portstr_[21]; char *portstr = NULL; char *namebuf = NULL; int derr; *r_dai = NULL; if (r_canonname) *r_canonname = NULL; memset (&hints, 0, sizeof hints); hints.ai_family = want_family; hints.ai_socktype = want_socktype; hints.ai_flags = AI_ADDRCONFIG; if (r_canonname) hints.ai_flags |= AI_CANONNAME; if (port) { snprintf (portstr_, sizeof portstr_, "%hu", port); portstr = portstr_; } err = libdns_res_open (&res); if (err) goto leave; if (is_ip_address (name)) { hints.ai_flags |= AI_NUMERICHOST; /* libdns does not grok brackets - remove them. */ if (*name == '[' && name[strlen(name)-1] == ']') { namebuf = xtrymalloc (strlen (name)); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } strcpy (namebuf, name+1); namebuf[strlen (namebuf)-1] = 0; name = namebuf; } } ai = dns_ai_open (name, portstr, 0, &hints, res, &derr); if (!ai) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Loop over all records. */ for (;;) { err = libdns_error_to_gpg_error (dns_ai_nextent (&ent, ai)); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (daihead) err = 0; /* We got some results, we're good. */ break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EAGAIN) { if (dns_ai_elapsed (ai) > opt_timeout) { err = gpg_error (GPG_ERR_DNS_TIMEOUT); goto leave; } my_unprotect (); dns_ai_poll (ai, 1); my_protect (); continue; } if (err) goto leave; if (r_canonname && ! *r_canonname && ent && ent->ai_canonname) { *r_canonname = xtrystrdup (ent->ai_canonname); if (!*r_canonname) { err = gpg_error_from_syserror (); goto leave; } /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_canonname && (*r_canonname)[strlen (*r_canonname)-1] == '.') (*r_canonname)[strlen (*r_canonname)-1] = 0; } dai = xtrymalloc (sizeof *dai); if (dai == NULL) { err = gpg_error_from_syserror (); goto leave; } dai->family = ent->ai_family; dai->socktype = ent->ai_socktype; dai->protocol = ent->ai_protocol; dai->addrlen = ent->ai_addrlen; memcpy (dai->addr, ent->ai_addr, ent->ai_addrlen); dai->next = daihead; daihead = dai; xfree (ent); } leave: dns_ai_close (ai); dns_res_close (res); if (err) { if (r_canonname) { xfree (*r_canonname); *r_canonname = NULL; } free_dns_addrinfo (daihead); } else *r_dai = daihead; xfree (namebuf); return err; } #endif /*USE_LIBDNS*/ /* Resolve a name using the standard system function. */ static gpg_error_t resolve_name_standard (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_dai, char **r_canonname) { gpg_error_t err = 0; dns_addrinfo_t daihead = NULL; dns_addrinfo_t dai; struct addrinfo *aibuf = NULL; struct addrinfo hints, *ai; char portstr[21]; int ret; *r_dai = NULL; if (r_canonname) *r_canonname = NULL; memset (&hints, 0, sizeof hints); hints.ai_family = want_family; hints.ai_socktype = want_socktype; hints.ai_flags = AI_ADDRCONFIG; if (r_canonname) hints.ai_flags |= AI_CANONNAME; if (is_ip_address (name)) hints.ai_flags |= AI_NUMERICHOST; if (port) snprintf (portstr, sizeof portstr, "%hu", port); else *portstr = 0; /* We can't use the AI_IDN flag because that does the conversion using the current locale. However, GnuPG always used UTF-8. To support IDN we would need to make use of the libidn API. */ ret = getaddrinfo (name, *portstr? portstr : NULL, &hints, &aibuf); if (ret) { aibuf = NULL; err = map_eai_to_gpg_error (ret); if (gpg_err_code (err) == GPG_ERR_NO_NAME) { /* There seems to be a bug in the glibc getaddrinfo function if the CNAME points to a long list of A and AAAA records in which case the function return NO_NAME. Let's do the CNAME redirection again. */ char *cname; if (get_dns_cname (name, &cname)) goto leave; /* Still no success. */ ret = getaddrinfo (cname, *portstr? portstr : NULL, &hints, &aibuf); xfree (cname); if (ret) { aibuf = NULL; err = map_eai_to_gpg_error (ret); goto leave; } err = 0; /* Yep, now it worked. */ } else goto leave; } if (r_canonname && aibuf && aibuf->ai_canonname) { *r_canonname = xtrystrdup (aibuf->ai_canonname); if (!*r_canonname) { err = gpg_error_from_syserror (); goto leave; } } for (ai = aibuf; ai; ai = ai->ai_next) { if (ai->ai_family != AF_INET6 && ai->ai_family != AF_INET) continue; if (opt_disable_ipv4 && ai->ai_family == AF_INET) continue; if (opt_disable_ipv6 && ai->ai_family == AF_INET6) continue; dai = xtrymalloc (sizeof *dai); dai->family = ai->ai_family; dai->socktype = ai->ai_socktype; dai->protocol = ai->ai_protocol; dai->addrlen = ai->ai_addrlen; memcpy (dai->addr, ai->ai_addr, ai->ai_addrlen); dai->next = daihead; daihead = dai; } leave: if (aibuf) freeaddrinfo (aibuf); if (err) { if (r_canonname) { xfree (*r_canonname); *r_canonname = NULL; } free_dns_addrinfo (daihead); } else *r_dai = daihead; return err; } /* This a wrapper around getaddrinfo with slightly different semantics. NAME is the name to resolve. PORT is the requested port or 0. WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM. On success the result is stored in a linked list with the head stored at the address R_AI; the caller must call gpg_addrinfo_free on this. If R_CANONNAME is not NULL the official name of the host is stored there as a malloced string; if that name is not available NULL is stored. */ gpg_error_t resolve_dns_name (const char *name, unsigned short port, int want_family, int want_socktype, dns_addrinfo_t *r_ai, char **r_canonname) { gpg_error_t err; #ifdef USE_LIBDNS if (!standard_resolver) { err = resolve_name_libdns (name, port, want_family, want_socktype, r_ai, r_canonname); if (err && libdns_switch_port_p (err)) err = resolve_name_libdns (name, port, want_family, want_socktype, r_ai, r_canonname); } else #endif /*USE_LIBDNS*/ err = resolve_name_standard (name, port, want_family, want_socktype, r_ai, r_canonname); if (opt_debug) log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err)); return err; } #ifdef USE_LIBDNS /* Resolve an address using libdns. */ static gpg_error_t resolve_addr_libdns (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; char host[DNS_D_MAXNAME + 1]; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_ptr ptr; int derr; *r_name = NULL; /* First we turn ADDR into a DNS name (with ".arpa" suffix). */ err = 0; if (addr->ss_family == AF_INET6) { const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr; if (!dns_aaaa_arpa (host, sizeof host, (void*)&a6->sin6_addr)) err = gpg_error (GPG_ERR_INV_OBJ); } else if (addr->ss_family == AF_INET) { const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr; if (!dns_a_arpa (host, sizeof host, (void*)&a4->sin_addr)) err = gpg_error (GPG_ERR_INV_OBJ); } else err = gpg_error (GPG_ERR_EAFNOSUPPORT); if (err) goto leave; err = libdns_res_open (&res); if (err) goto leave; err = libdns_res_submit (res, host, DNS_T_PTR, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; goto leave; } /* Parse the result. */ if (!err) { struct dns_rr rr; struct dns_rr_i rri; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_PTR; if (!dns_rr_grep (&rr, 1, &rri, ans, &derr)) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } err = libdns_error_to_gpg_error (dns_ptr_parse (&ptr, &rr, ans)); if (err) goto leave; /* Copy result. */ *r_name = xtrystrdup (ptr.host); if (!*r_name) { err = gpg_error_from_syserror (); goto leave; } /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_name && (*r_name)[strlen (*r_name)-1] == '.') (*r_name)[strlen (*r_name)-1] = 0; } else /* GPG_ERR_NO_NAME */ { char *buffer, *p; int buflen; int ec; buffer = ptr.host; buflen = sizeof ptr.host; p = buffer; if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) { *p++ = '['; buflen -= 2; } ec = getnameinfo ((const struct sockaddr *)addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); if (ec) { err = map_eai_to_gpg_error (ec); goto leave; } if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) strcat (buffer, "]"); } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Resolve an address using the standard system function. */ static gpg_error_t resolve_addr_standard (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; int ec; char *buffer, *p; int buflen; *r_name = NULL; buflen = NI_MAXHOST; buffer = xtrymalloc (buflen + 2 + 1); if (!buffer) return gpg_error_from_syserror (); if ((flags & DNS_NUMERICHOST) || tor_mode) ec = EAI_NONAME; else ec = getnameinfo ((const struct sockaddr *)addr, addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD); if (!ec && *buffer == '[') ec = EAI_FAIL; /* A name may never start with a bracket. */ else if (ec == EAI_NONAME) { p = buffer; if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) { *p++ = '['; buflen -= 2; } ec = getnameinfo ((const struct sockaddr *)addr, addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); if (!ec && addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) strcat (buffer, "]"); } if (ec) err = map_eai_to_gpg_error (ec); else { p = xtryrealloc (buffer, strlen (buffer)+1); if (!p) err = gpg_error_from_syserror (); else { buffer = p; err = 0; } } if (err) xfree (buffer); else *r_name = buffer; return err; } /* A wrapper around getnameinfo. */ gpg_error_t resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen, unsigned int flags, char **r_name) { gpg_error_t err; #ifdef USE_LIBDNS /* Note that we divert to the standard resolver for NUMERICHOST. */ if (!standard_resolver && !(flags & DNS_NUMERICHOST)) { err = resolve_addr_libdns (addr, addrlen, flags, r_name); if (err && libdns_switch_port_p (err)) err = resolve_addr_libdns (addr, addrlen, flags, r_name); } else #endif /*USE_LIBDNS*/ err = resolve_addr_standard (addr, addrlen, flags, r_name); if (opt_debug) log_debug ("dns: resolve_dns_addr(): %s\n", gpg_strerror (err)); return err; } /* Check whether NAME is an IP address. Returns a true if it is * either an IPv6 or a IPv4 numerical address. The actual return * values can also be used to identify whether it is v4 or v6: The * true value will surprisingly be 4 for IPv4 and 6 for IPv6. */ int is_ip_address (const char *name) { const char *s; int ndots, dblcol, n; if (*name == '[') return 6; /* yes: A legal DNS name may not contain this character; this must be bracketed v6 address. */ if (*name == '.') return 0; /* No. A leading dot is not a valid IP address. */ /* Check whether this is a v6 address. */ ndots = n = dblcol = 0; for (s=name; *s; s++) { if (*s == ':') { ndots++; if (s[1] == ':') { ndots++; if (dblcol) return 0; /* No: Only one "::" allowed. */ dblcol++; if (s[1]) s++; } n = 0; } else if (*s == '.') goto legacy; else if (!strchr ("0123456789abcdefABCDEF", *s)) return 0; /* No: Not a hex digit. */ else if (++n > 4) return 0; /* To many digits in a group. */ } if (ndots > 7) return 0; /* No: Too many colons. */ else if (ndots > 1) return 6; /* Yes: At least 2 colons indicate an v6 address. */ legacy: /* Check whether it is legacy IP address. */ ndots = n = 0; for (s=name; *s; s++) { if (*s == '.') { if (s[1] == '.') return 0; /* No: Double dot. */ if (atoi (s+1) > 255) return 0; /* No: Ipv4 byte value too large. */ ndots++; n = 0; } else if (!strchr ("0123456789", *s)) return 0; /* No: Not a digit. */ else if (++n > 3) return 0; /* No: More than 3 digits. */ } return (ndots == 3)? 4 : 0; } /* Return true if NAME is an onion address. */ int is_onion_address (const char *name) { size_t len; len = name? strlen (name) : 0; if (len < 8 || strcmp (name + len - 6, ".onion")) return 0; /* Note that we require at least 2 characters before the suffix. */ return 1; /* Yes. */ } /* libdns version of get_dns_cert. */ #ifdef USE_LIBDNS static gpg_error_t get_dns_cert_libdns (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_rr rr; struct dns_rr_i rri; char host[DNS_D_MAXNAME + 1]; int derr; int qtype; /* Get the query type from WANT_CERTTYPE (which in general indicates * the subtype we want). */ qtype = (want_certtype < DNS_CERTTYPE_RRBASE ? T_CERT : (want_certtype - DNS_CERTTYPE_RRBASE)); err = libdns_res_open (&res); if (err) goto leave; if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) { err = gpg_error (GPG_ERR_ENAMETOOLONG); goto leave; } err = libdns_res_submit (res, name, qtype, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = qtype; err = gpg_error (GPG_ERR_NOT_FOUND); while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) { unsigned char *rp = ans->data + rr.rd.p; unsigned short len = rr.rd.len; u16 subtype; if (!len) { /* Definitely too short - skip. */ } else if (want_certtype >= DNS_CERTTYPE_RRBASE && rr.type == (want_certtype - DNS_CERTTYPE_RRBASE) && r_key) { *r_key = xtrymalloc (len); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, rp, len); *r_keylen = len; err = 0; } goto leave; } else if (want_certtype >= DNS_CERTTYPE_RRBASE) { /* We did not found the requested RR - skip. */ } else if (rr.type == T_CERT && len > 5) { /* We got a CERT type. */ subtype = buf16_to_u16 (rp); rp += 2; len -= 2; /* Skip the CERT key tag and algo which we don't need. */ rp += 3; len -= 3; if (want_certtype && want_certtype != subtype) ; /* Not the requested subtype - skip. */ else if (subtype == DNS_CERTTYPE_PGP && len && r_key && r_keylen) { /* PGP subtype */ *r_key = xtrymalloc (len); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, rp, len); *r_keylen = len; err = 0; } goto leave; } else if (subtype == DNS_CERTTYPE_IPGP && len && len < 1023 && len >= rp[0] + 1) { /* IPGP type */ *r_fprlen = rp[0]; if (*r_fprlen) { *r_fpr = xtrymalloc (*r_fprlen); if (!*r_fpr) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_fpr, rp+1, *r_fprlen); } else *r_fpr = NULL; if (len > *r_fprlen + 1) { *r_url = xtrymalloc (len - (*r_fprlen + 1) + 1); if (!*r_url) { err = gpg_error_from_syserror (); xfree (*r_fpr); *r_fpr = NULL; goto leave; } memcpy (*r_url, rp + *r_fprlen + 1, len - (*r_fprlen + 1)); (*r_url)[len - (*r_fprlen + 1)] = 0; } else *r_url = NULL; err = 0; goto leave; } else { /* Unknown subtype or record too short - skip. */ } } else { /* Not a requested type - skip. */ } } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver version of get_dns_cert. */ static gpg_error_t get_dns_cert_standard (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { #ifdef HAVE_SYSTEM_RESOLVER gpg_error_t err; unsigned char *answer; int r; u16 count; /* Allocate a 64k buffer which is the limit for an DNS response. */ answer = xtrymalloc (65536); if (!answer) return gpg_error_from_syserror (); err = gpg_error (GPG_ERR_NOT_FOUND); r = res_query (name, C_IN, (want_certtype < DNS_CERTTYPE_RRBASE ? T_CERT : (want_certtype - DNS_CERTTYPE_RRBASE)), answer, 65536); /* Not too big, not too small, no errors and at least 1 answer. */ if (r >= sizeof (HEADER) && r <= 65536 && (((HEADER *)(void *) answer)->rcode) == NOERROR && (count = ntohs (((HEADER *)(void *) answer)->ancount))) { int rc; unsigned char *pt, *emsg; emsg = &answer[r]; pt = &answer[sizeof (HEADER)]; /* Skip over the query */ rc = dn_skipname (pt, emsg); if (rc == -1) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } pt += rc + QFIXEDSZ; /* There are several possible response types for a CERT request. We're interested in the PGP (a key) and IPGP (a URI) types. Skip all others. TODO: A key is better than a URI since we've gone through all this bother to fetch it, so favor that if we have both PGP and IPGP? */ while (count-- > 0 && pt < emsg) { u16 type, class, dlen, ctype; rc = dn_skipname (pt, emsg); /* the name we just queried for */ if (rc == -1) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } pt += rc; /* Truncated message? 15 bytes takes us to the point where we start looking at the ctype. */ if ((emsg - pt) < 15) break; type = buf16_to_u16 (pt); pt += 2; class = buf16_to_u16 (pt); pt += 2; if (class != C_IN) break; /* ttl */ pt += 4; /* data length */ dlen = buf16_to_u16 (pt); pt += 2; /* Check the type and parse. */ if (want_certtype >= DNS_CERTTYPE_RRBASE && type == (want_certtype - DNS_CERTTYPE_RRBASE) && r_key) { *r_key = xtrymalloc (dlen); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, pt, dlen); *r_keylen = dlen; err = 0; } goto leave; } else if (want_certtype >= DNS_CERTTYPE_RRBASE) { /* We did not found the requested RR. */ pt += dlen; } else if (type == T_CERT) { /* We got a CERT type. */ ctype = buf16_to_u16 (pt); pt += 2; /* Skip the CERT key tag and algo which we don't need. */ pt += 3; dlen -= 5; /* 15 bytes takes us to here */ if (want_certtype && want_certtype != ctype) ; /* Not of the requested certtype. */ else if (ctype == DNS_CERTTYPE_PGP && dlen && r_key && r_keylen) { /* PGP type */ *r_key = xtrymalloc (dlen); if (!*r_key) err = gpg_error_from_syserror (); else { memcpy (*r_key, pt, dlen); *r_keylen = dlen; err = 0; } goto leave; } else if (ctype == DNS_CERTTYPE_IPGP && dlen && dlen < 1023 && dlen >= pt[0] + 1) { /* IPGP type */ *r_fprlen = pt[0]; if (*r_fprlen) { *r_fpr = xtrymalloc (*r_fprlen); if (!*r_fpr) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_fpr, &pt[1], *r_fprlen); } else *r_fpr = NULL; if (dlen > *r_fprlen + 1) { *r_url = xtrymalloc (dlen - (*r_fprlen + 1) + 1); if (!*r_url) { err = gpg_error_from_syserror (); xfree (*r_fpr); *r_fpr = NULL; goto leave; } memcpy (*r_url, &pt[*r_fprlen + 1], dlen - (*r_fprlen + 1)); (*r_url)[dlen - (*r_fprlen + 1)] = '\0'; } else *r_url = NULL; err = 0; goto leave; } /* No subtype matches, so continue with the next answer. */ pt += dlen; } else { /* Not a requested type - might be a CNAME. Try next item. */ pt += dlen; } } } leave: xfree (answer); return err; #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)want_certtype; (void)r_key; (void)r_keylen; (void)r_fpr; (void)r_fprlen; (void)r_url; return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } /* Returns 0 on success or an error code. If a PGP CERT record was found, the malloced data is returned at (R_KEY, R_KEYLEN) and the other return parameters are set to NULL/0. If an IPGP CERT record was found the fingerprint is stored as an allocated block at R_FPR and its length at R_FPRLEN; an URL is allocated as a string and returned at R_URL. If WANT_CERTTYPE is 0 this function returns the first CERT found with a supported type; it is expected that only one CERT record is used. If WANT_CERTTYPE is one of the supported certtypes only records with this certtype are considered and the first found is returned. (R_KEY,R_KEYLEN) are optional. */ gpg_error_t get_dns_cert (const char *name, int want_certtype, void **r_key, size_t *r_keylen, unsigned char **r_fpr, size_t *r_fprlen, char **r_url) { gpg_error_t err; if (r_key) *r_key = NULL; if (r_keylen) *r_keylen = 0; *r_fpr = NULL; *r_fprlen = 0; *r_url = NULL; #ifdef USE_LIBDNS if (!standard_resolver) { err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); if (err && libdns_switch_port_p (err)) err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); } else #endif /*USE_LIBDNS*/ err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen, r_fpr, r_fprlen, r_url); if (opt_debug) log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err)); return err; } static int priosort(const void *a,const void *b) { const struct srventry *sa=a,*sb=b; if(sa->priority>sb->priority) return 1; else if(sa->prioritypriority) return -1; else return 0; } /* Libdns based helper for getsrv. Note that it is expected that NULL * is stored at the address of LIST and 0 is stored at the address of * R_COUNT. */ #ifdef USE_LIBDNS static gpg_error_t getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count) { gpg_error_t err; struct dns_resolver *res = NULL; struct dns_packet *ans = NULL; struct dns_rr rr; struct dns_rr_i rri; char host[DNS_D_MAXNAME + 1]; int derr; unsigned int srvcount = 0; err = libdns_res_open (&res); if (err) goto leave; if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) { err = gpg_error (GPG_ERR_ENAMETOOLONG); goto leave; } err = libdns_res_submit (res, name, DNS_T_SRV, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; memset (&rri, 0, sizeof rri); dns_rr_i_init (&rri, ans); rri.section = DNS_S_ALL & ~DNS_S_QD; rri.name = host; rri.type = DNS_T_SRV; while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) { struct dns_srv dsrv; struct srventry *srv; struct srventry *newlist; err = libdns_error_to_gpg_error (dns_srv_parse(&dsrv, &rr, ans)); if (err) goto leave; newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); if (!newlist) { err = gpg_error_from_syserror (); goto leave; } *list = newlist; memset (&(*list)[srvcount], 0, sizeof(struct srventry)); srv = &(*list)[srvcount]; srvcount++; srv->priority = dsrv.priority; srv->weight = dsrv.weight; srv->port = dsrv.port; mem2str (srv->target, dsrv.target, sizeof srv->target); /* Libdns appends the root zone part which is problematic for * most other functions - strip it. */ if (*srv->target && (srv->target)[strlen (srv->target)-1] == '.') (srv->target)[strlen (srv->target)-1] = 0; } *r_count = srvcount; leave: if (err) { xfree (*list); *list = NULL; } dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver based helper for getsrv. Note that it is * expected that NULL is stored at the address of LIST and 0 is stored * at the address of R_COUNT. */ static gpg_error_t getsrv_standard (const char *name, struct srventry **list, unsigned int *r_count) { #ifdef HAVE_SYSTEM_RESOLVER union { unsigned char ans[2048]; HEADER header[1]; } res; unsigned char *answer = res.ans; HEADER *header = res.header; unsigned char *pt, *emsg; int r, rc; u16 dlen; unsigned int srvcount = 0; u16 count; /* Do not allow a query using the standard resolver in Tor mode. */ if (tor_mode) return gpg_error (GPG_ERR_NOT_ENABLED); my_unprotect (); r = res_query (name, C_IN, T_SRV, answer, sizeof res.ans); my_protect (); if (r < 0) return get_h_errno_as_gpg_error (); if (r < sizeof (HEADER)) return gpg_error (GPG_ERR_SERVER_FAILED); if (r > sizeof res.ans) return gpg_error (GPG_ERR_SYSTEM_BUG); if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ emsg = &answer[r]; pt = &answer[sizeof(HEADER)]; /* Skip over the query */ rc = dn_skipname (pt, emsg); if (rc == -1) goto fail; pt += rc + QFIXEDSZ; while (count-- > 0 && pt < emsg) { struct srventry *srv; u16 type, class; struct srventry *newlist; newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); if (!newlist) goto fail; *list = newlist; memset (&(*list)[srvcount], 0, sizeof(struct srventry)); srv = &(*list)[srvcount]; srvcount++; rc = dn_skipname (pt, emsg); /* The name we just queried for. */ if (rc == -1) goto fail; pt += rc; /* Truncated message? */ if ((emsg-pt) < 16) goto fail; type = buf16_to_u16 (pt); pt += 2; /* We asked for SRV and got something else !? */ if (type != T_SRV) goto fail; class = buf16_to_u16 (pt); pt += 2; /* We asked for IN and got something else !? */ if (class != C_IN) goto fail; pt += 4; /* ttl */ dlen = buf16_to_u16 (pt); pt += 2; srv->priority = buf16_to_ushort (pt); pt += 2; srv->weight = buf16_to_ushort (pt); pt += 2; srv->port = buf16_to_ushort (pt); pt += 2; /* Get the name. 2782 doesn't allow name compression, but * dn_expand still works to pull the name out of the packet. */ rc = dn_expand (answer, emsg, pt, srv->target, sizeof srv->target); if (rc == 1 && srv->target[0] == 0) /* "." */ { xfree(*list); *list = NULL; return 0; } if (rc == -1) goto fail; pt += rc; /* Corrupt packet? */ if (dlen != rc+6) goto fail; } *r_count = srvcount; return 0; fail: xfree (*list); *list = NULL; return gpg_error (GPG_ERR_GENERAL); #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)list; (void)r_count; return gpg_error (GPG_ERR_NOT_SUPPORTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } /* Query a SRV record for SERVICE and PROTO for NAME. If SERVICE is * NULL, NAME is expected to contain the full query name. Note that * we do not return NONAME but simply store 0 at R_COUNT. On error an * error code is returned and 0 stored at R_COUNT. */ gpg_error_t get_dns_srv (const char *name, const char *service, const char *proto, struct srventry **list, unsigned int *r_count) { gpg_error_t err; char *namebuffer = NULL; unsigned int srvcount; int i; *list = NULL; *r_count = 0; srvcount = 0; /* If SERVICE is given construct the query from it and PROTO. */ if (service) { namebuffer = xtryasprintf ("_%s._%s.%s", service, proto? proto:"tcp", name); if (!namebuffer) { err = gpg_error_from_syserror (); goto leave; } name = namebuffer; } #ifdef USE_LIBDNS if (!standard_resolver) { err = getsrv_libdns (name, list, &srvcount); if (err && libdns_switch_port_p (err)) err = getsrv_libdns (name, list, &srvcount); } else #endif /*USE_LIBDNS*/ err = getsrv_standard (name, list, &srvcount); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_NAME) err = 0; goto leave; } /* Now we have an array of all the srv records. */ /* Order by priority */ qsort(*list,srvcount,sizeof(struct srventry),priosort); /* For each priority, move the zero-weighted items first. */ for (i=0; i < srvcount; i++) { int j; for (j=i;j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) { if((*list)[j].weight==0) { /* Swap j with i */ if(j!=i) { struct srventry temp; memcpy (&temp,&(*list)[j],sizeof(struct srventry)); memcpy (&(*list)[j],&(*list)[i],sizeof(struct srventry)); memcpy (&(*list)[i],&temp,sizeof(struct srventry)); } break; } } } /* Run the RFC-2782 weighting algorithm. We don't need very high quality randomness for this, so regular libc srand/rand is sufficient. */ { static int done; if (!done) { done = 1; srand (time (NULL)*getpid()); } } for (i=0; i < srvcount; i++) { int j; float prio_count=0,chose; for (j=i; j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) { prio_count+=(*list)[j].weight; (*list)[j].run_count=prio_count; } chose=prio_count*rand()/RAND_MAX; for (j=i;j %u records\n", name, srvcount); } if (!err) *r_count = srvcount; xfree (namebuffer); return err; } #ifdef USE_LIBDNS /* libdns version of get_dns_cname. */ gpg_error_t get_dns_cname_libdns (const char *name, char **r_cname) { gpg_error_t err; struct dns_resolver *res; struct dns_packet *ans = NULL; struct dns_cname cname; int derr; err = libdns_res_open (&res); if (err) goto leave; err = libdns_res_submit (res, name, DNS_T_CNAME, DNS_C_IN); if (err) goto leave; err = libdns_res_wait (res); if (err) goto leave; ans = dns_res_fetch (res, &derr); if (!ans) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Check the rcode. */ switch (dns_p_rcode (ans)) { case DNS_RC_NOERROR: break; case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; default: err = GPG_ERR_SERVER_FAILED; break; } if (err) goto leave; /* Parse the result into CNAME. */ err = libdns_error_to_gpg_error (dns_p_study (ans)); if (err) goto leave; if (!dns_d_cname (&cname, sizeof cname, name, strlen (name), ans, &derr)) { err = libdns_error_to_gpg_error (derr); goto leave; } /* Copy result. */ *r_cname = xtrystrdup (cname.host); if (!*r_cname) err = gpg_error_from_syserror (); else { /* Libdns appends the root zone part which is problematic * for most other functions - strip it. */ if (**r_cname && (*r_cname)[strlen (*r_cname)-1] == '.') (*r_cname)[strlen (*r_cname)-1] = 0; } leave: dns_free (ans); dns_res_close (res); return err; } #endif /*USE_LIBDNS*/ /* Standard resolver version of get_dns_cname. */ gpg_error_t get_dns_cname_standard (const char *name, char **r_cname) { #ifdef HAVE_SYSTEM_RESOLVER gpg_error_t err; int rc; union { unsigned char ans[2048]; HEADER header[1]; } res; unsigned char *answer = res.ans; HEADER *header = res.header; unsigned char *pt, *emsg; int r; char *cname; int cnamesize = 1025; u16 count; /* Do not allow a query using the standard resolver in Tor mode. */ if (tor_mode) return -1; my_unprotect (); r = res_query (name, C_IN, T_CERT, answer, sizeof res.ans); my_protect (); if (r < 0) return get_h_errno_as_gpg_error (); if (r < sizeof (HEADER)) return gpg_error (GPG_ERR_SERVER_FAILED); if (r > sizeof res.ans) return gpg_error (GPG_ERR_SYSTEM_BUG); if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ if (count != 1) return gpg_error (GPG_ERR_SERVER_FAILED); emsg = &answer[r]; pt = &answer[sizeof(HEADER)]; rc = dn_skipname (pt, emsg); if (rc == -1) return gpg_error (GPG_ERR_SERVER_FAILED); pt += rc + QFIXEDSZ; if (pt >= emsg) return gpg_error (GPG_ERR_SERVER_FAILED); rc = dn_skipname (pt, emsg); if (rc == -1) return gpg_error (GPG_ERR_SERVER_FAILED); pt += rc + 2 + 2 + 4; if (pt+2 >= emsg) return gpg_error (GPG_ERR_SERVER_FAILED); pt += 2; /* Skip rdlen */ cname = xtrymalloc (cnamesize); if (!cname) return gpg_error_from_syserror (); rc = dn_expand (answer, emsg, pt, cname, cnamesize -1); if (rc == -1) { xfree (cname); return gpg_error (GPG_ERR_SERVER_FAILED); } *r_cname = xtryrealloc (cname, strlen (cname)+1); if (!*r_cname) { err = gpg_error_from_syserror (); xfree (cname); return err; } return 0; #else /*!HAVE_SYSTEM_RESOLVER*/ (void)name; (void)r_cname; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif /*!HAVE_SYSTEM_RESOLVER*/ } gpg_error_t get_dns_cname (const char *name, char **r_cname) { gpg_error_t err; *r_cname = NULL; #ifdef USE_LIBDNS if (!standard_resolver) { err = get_dns_cname_libdns (name, r_cname); if (err && libdns_switch_port_p (err)) err = get_dns_cname_libdns (name, r_cname); return err; } #endif /*USE_LIBDNS*/ err = get_dns_cname_standard (name, r_cname); if (opt_debug) log_debug ("get_dns_cname(%s)%s%s\n", name, err ? ": " : " -> ", err ? gpg_strerror (err) : *r_cname); return err; } diff --git a/dirmngr/dns.c b/dirmngr/dns.c index 8e8b6db0b..77f83f437 100644 --- a/dirmngr/dns.c +++ b/dirmngr/dns.c @@ -1,11486 +1,11509 @@ /* ========================================================================== * dns.c - Recursive, Reentrant DNS Resolver. * -------------------------------------------------------------------------- * Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, subject to the * following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * USE OR OTHER DEALINGS IN THE SOFTWARE. * ========================================================================== */ #if HAVE_CONFIG_H #include "config.h" #elif !defined _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include /* INT_MAX */ #include /* va_list va_start va_end */ #include /* offsetof() */ #ifdef _WIN32 /* JW: This breaks our mingw build: #define uint32_t unsigned int */ #else #include /* uint32_t */ #endif #include /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */ #include /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */ #include /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */ #include /* strcasecmp(3) strncasecmp(3) */ #include /* isspace(3) isdigit(3) */ #include /* time_t time(2) difftime(3) */ #include /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */ #include /* errno EINVAL ENOENT */ #undef NDEBUG #include /* assert(3) */ #if _WIN32 #ifndef FD_SETSIZE #define FD_SETSIZE 1024 #endif #include #include typedef SOCKET socket_fd_t; #define STDCALL __stdcall #ifdef TIME_WITH_SYS_TIME #include /* gettimeofday(2) */ #endif #else typedef int socket_fd_t; #define STDCALL #include /* gettimeofday(2) */ #include /* FD_SETSIZE socklen_t */ #include /* FD_ZERO FD_SET fd_set select(2) */ #include /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */ #if defined(AF_UNIX) #include /* struct sockaddr_un */ #endif #include /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */ #include /* _POSIX_THREADS gethostname(3) close(2) */ #include /* POLLIN POLLOUT */ #include /* struct sockaddr_in struct sockaddr_in6 */ #include /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */ #include /* struct addrinfo */ #endif #include "dns.h" /* * C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p)) #define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p))) #define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p)) #define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p))) #define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p) #if defined __has_builtin #define dns_has_builtin(x) __has_builtin(x) #else #define dns_has_builtin(x) 0 #endif #if defined __has_extension #define dns_has_extension(x) __has_extension(x) #else #define dns_has_extension(x) 0 #endif #ifndef HAVE___ASSUME #define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0) #endif #ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P #define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__) #endif #ifndef HAVE___BUILTIN_UNREACHABLE #define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable)) #endif #ifndef HAVE_PRAGMA_MESSAGE #define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER) #endif /* * C O M P I L E R A N N O T A T I O N S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if __GNUC__ #define DNS_NOTUSED __attribute__((unused)) #define DNS_NORETURN __attribute__((noreturn)) #else #define DNS_NOTUSED #define DNS_NORETURN #endif #if __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wmissing-field-initializers" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif /* * S T A N D A R D M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if HAVE___BUILTIN_TYPES_COMPATIBLE_P #define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b)) #else #define dns_same_type(a, b, def) (def) #endif #define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0)) /* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */ #define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; }))) #if HAVE___ASSUME #define dns_assume(cond) __assume(cond) #elif HAVE___BUILTIN_UNREACHABLE #define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) #else #define dns_assume(cond) do { (void)(cond); } while (0) #endif #ifndef lengthof #define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0])) #endif #ifndef endof #define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))]) #endif /* * M I S C E L L A N E O U S C O M P A T * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if _WIN32 || _WIN64 #define PRIuZ "Iu" #else #define PRIuZ "zu" #endif #ifndef DNS_THREAD_SAFE #if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0 #define DNS_THREAD_SAFE 1 #else #define DNS_THREAD_SAFE 0 #endif #endif #ifndef HAVE__STATIC_ASSERT #define HAVE__STATIC_ASSERT \ (dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \ __C11FEATURES__ || __STDC_VERSION__ >= 201112L) #endif #ifndef HAVE_STATIC_ASSERT #if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0) #define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */ #elif defined(static_assert) #define HAVE_STATIC_ASSERT 1 #else #define HAVE_STATIC_ASSERT 0 #endif #endif #if HAVE_STATIC_ASSERT #define dns_static_assert(cond, msg) static_assert(cond, msg) #elif HAVE__STATIC_ASSERT #define dns_static_assert(cond, msg) _Static_assert(cond, msg) #else #define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])] #endif /* * D E B U G M A C R O S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int *dns_debug_p(void) { static int debug; return &debug; } /* dns_debug_p() */ #if DNS_DEBUG #undef DNS_DEBUG #define DNS_DEBUG dns_debug #define DNS_SAY_(fmt, ...) \ do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0) #define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n") #define DNS_HAI DNS_SAY("HAI") #define DNS_SHOW_(P, fmt, ...) do { \ if (DNS_DEBUG > 1) { \ fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \ fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \ dns_p_dump((P), stderr); \ fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \ } \ } while (0) #define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "") #else /* !DNS_DEBUG */ #undef DNS_DEBUG #define DNS_DEBUG 0 #define DNS_SAY(...) #define DNS_HAI #define DNS_SHOW(...) #endif /* DNS_DEBUG */ #define DNS_CARP(...) DNS_SAY(__VA_ARGS__) /* * V E R S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ const char *dns_vendor(void) { return DNS_VENDOR; } /* dns_vendor() */ int dns_v_rel(void) { return DNS_V_REL; } /* dns_v_rel() */ int dns_v_abi(void) { return DNS_V_ABI; } /* dns_v_abi() */ int dns_v_api(void) { return DNS_V_API; } /* dns_v_api() */ /* * E R R O R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef EPROTO # define EPROTO EPROTONOSUPPORT #endif #if _WIN32 #define DNS_EINTR WSAEINTR #define DNS_EINPROGRESS WSAEINPROGRESS #define DNS_EISCONN WSAEISCONN #define DNS_EWOULDBLOCK WSAEWOULDBLOCK #define DNS_EALREADY WSAEALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT WSAETIMEDOUT #define dns_syerr() ((int)GetLastError()) #define dns_soerr() ((int)WSAGetLastError()) #else #define DNS_EINTR EINTR #define DNS_EINPROGRESS EINPROGRESS #define DNS_EISCONN EISCONN #define DNS_EWOULDBLOCK EWOULDBLOCK #define DNS_EALREADY EALREADY #define DNS_EAGAIN EAGAIN #define DNS_ETIMEDOUT ETIMEDOUT #define dns_syerr() errno #define dns_soerr() errno #endif const char *dns_strerror(int error) { switch (error) { case DNS_ENOBUFS: return "DNS packet buffer too small"; case DNS_EILLEGAL: return "Illegal DNS RR name or data"; case DNS_EORDER: return "Attempt to push RR out of section order"; case DNS_ESECTION: return "Invalid section specified"; case DNS_EUNKNOWN: return "Unknown DNS error"; case DNS_EADDRESS: return "Invalid textual address form"; case DNS_ENOQUERY: return "Bad execution state (missing query packet)"; case DNS_ENOANSWER: return "Bad execution state (missing answer packet)"; case DNS_EFETCHED: return "Answer already fetched"; case DNS_ESERVICE: return "The service passed was not recognized for the specified socket type"; case DNS_ENONAME: return "The name does not resolve for the supplied parameters"; case DNS_EFAIL: return "A non-recoverable error occurred when attempting to resolve the name"; case DNS_ECONNFIN: return "Connection closed"; case DNS_EVERIFY: return "Reply failed verification"; default: return strerror(error); } /* switch() */ } /* dns_strerror() */ /* * A T O M I C R O U T I N E S * * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we * can use the preprocessor to detect API and, more importantly, ISA * support. We want to avoid linking headaches where the API depends on an * external library if the ISA (e.g. i386) doesn't support lockless * operation. * * TODO: Support C11's atomic API. Although that may require some finesse * with how we define some public types, such as dns_atomic_t and struct * dns_resolv_conf. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HAVE___ATOMIC_FETCH_ADD #ifdef __ATOMIC_RELAXED #define HAVE___ATOMIC_FETCH_ADD 1 #else #define HAVE___ATOMIC_FETCH_ADD 0 #endif #endif #ifndef HAVE___ATOMIC_FETCH_SUB #define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD #endif #ifndef DNS_ATOMIC_FETCH_ADD #if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_add available") #define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++) #endif #endif #ifndef DNS_ATOMIC_FETCH_SUB #if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2 #define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED) #else #pragma message("no atomic_fetch_sub available") #define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--) #endif #endif static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_ADD(i); } /* dns_atomic_fetch_add() */ static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) { return DNS_ATOMIC_FETCH_SUB(i); } /* dns_atomic_fetch_sub() */ /* * C R Y P T O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * P R N G */ #ifndef DNS_RANDOM #if defined(HAVE_ARC4RANDOM) \ || defined(__OpenBSD__) \ || defined(__FreeBSD__) \ || defined(__NetBSD__) \ || defined(__APPLE__) #define DNS_RANDOM arc4random #elif __linux #define DNS_RANDOM random #else #define DNS_RANDOM rand #endif #endif #define DNS_RANDOM_arc4random 1 #define DNS_RANDOM_random 2 #define DNS_RANDOM_rand 3 #define DNS_RANDOM_RAND_bytes 4 #define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM)) #if DNS_RANDOM_OPENSSL #include #endif static unsigned dns_random_(void) { #if DNS_RANDOM_OPENSSL unsigned r; _Bool ok; ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r)); assert(ok && "1 == RAND_bytes()"); return r; #else return DNS_RANDOM(); #endif } /* dns_random_() */ dns_random_f **dns_random_p(void) { static dns_random_f *random_f = &dns_random_; return &random_f; } /* dns_random_p() */ /* * P E R M U T A T I O N G E N E R A T O R */ #define DNS_K_TEA_KEY_SIZE 16 #define DNS_K_TEA_BLOCK_SIZE 8 #define DNS_K_TEA_CYCLES 32 #define DNS_K_TEA_MAGIC 0x9E3779B9U struct dns_k_tea { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned cycles; }; /* struct dns_k_tea */ static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) { memcpy(tea->key, key, sizeof tea->key); tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES; } /* dns_k_tea_init() */ static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) { uint32_t y, z, sum, n; y = v[0]; z = v[1]; sum = 0; for (n = 0; n < tea->cycles; n++) { sum += DNS_K_TEA_MAGIC; y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]); z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]); } w[0] = y; w[1] = z; return /* void */; } /* dns_k_tea_encrypt() */ /* * Permutation generator, based on a Luby-Rackoff Feistel construction. * * Specifically, this is a generic balanced Feistel block cipher using TEA * (another block cipher) as the pseudo-random function, F. At best it's as * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or * perhaps Bernstein's Salsa20 core; I am naively trying to keep things * simple. * * The generator can create a permutation of any set of numbers, as long as * the size of the set is an even power of 2. This limitation arises either * out of an inherent property of balanced Feistel constructions, or by my * own ignorance. I'll tackle an unbalanced construction after I wrap my * head around Schneier and Kelsey's paper. * * CAVEAT EMPTOR. IANAC. */ #define DNS_K_PERMUTOR_ROUNDS 8 struct dns_k_permutor { unsigned stepi, length, limit; unsigned shift, mask, rounds; struct dns_k_tea tea; }; /* struct dns_k_permutor */ static inline unsigned dns_k_permutor_powof(unsigned n) { unsigned m, i = 0; for (m = 1; m < n; m <<= 1, i++) ;; return i; } /* dns_k_permutor_powof() */ static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) { uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; unsigned width, i; p->stepi = 0; p->length = (high - low) + 1; p->limit = high; width = dns_k_permutor_powof(p->length); width += width % 2; p->shift = width / 2; p->mask = (1U << p->shift) - 1; p->rounds = DNS_K_PERMUTOR_ROUNDS; for (i = 0; i < lengthof(key); i++) key[i] = dns_random(); dns_k_tea_init(&p->tea, key, 0); return /* void */; } /* dns_k_permutor_init() */ static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) { uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)]; memset(in, '\0', sizeof in); in[0] = k; in[1] = x; dns_k_tea_encrypt(&p->tea, in, out); return p->mask & out[0]; } /* dns_k_permutor_F() */ static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = 0; l[i] = p->mask & (n >> p->shift); r[i] = p->mask & (n >> 0); do { l[(i + 1) % 2] = r[i % 2]; r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]); i++; } while (i < p->rounds - 1); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_E() */ DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) { unsigned l[2], r[2]; unsigned i; i = p->rounds - 1; l[i % 2] = p->mask & (n >> p->shift); r[i % 2] = p->mask & (n >> 0); do { i--; r[i % 2] = l[(i + 1) % 2]; l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]); } while (i > 0); return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); } /* dns_k_permutor_D() */ static unsigned dns_k_permutor_step(struct dns_k_permutor *p) { unsigned n; do { n = dns_k_permutor_E(p, p->stepi++); } while (n >= p->length); return n + (p->limit + 1 - p->length); } /* dns_k_permutor_step() */ /* * Simple permutation box. Useful for shuffling rrsets from an iterator. * Uses AES s-box to provide good diffusion. * * Seems to pass muster under runs test. * * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }' * library(lawstat) * runs.test(scan(file="/tmp/out")) * EOF */ static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) { static const unsigned char sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; unsigned char a, b; unsigned i; a = 0xff & (n >> 0); b = 0xff & (n >> 8); for (i = 0; i < 4; i++) { a ^= 0xff & s; a = sbox[a] ^ b; b = sbox[b] ^ a; s >>= 8; } return ((0xff00 & (a << 8)) | (0x00ff & (b << 0))); } /* dns_k_shuffle16() */ /* * S T A T E M A C H I N E R O U T I N E S * * Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the * local variable pc. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_SM_ENTER \ do { \ static const int pc0 = __LINE__; \ DNS_SM_RESTORE; \ switch (pc0 + pc) { \ case __LINE__: (void)0 #define DNS_SM_SAVE_AND_DO(do_statement) \ do { \ pc = __LINE__ - pc0; \ DNS_SM_SAVE; \ do_statement; \ case __LINE__: (void)0; \ } while (0) #define DNS_SM_YIELD(rv) \ DNS_SM_SAVE_AND_DO(return (rv)) #define DNS_SM_EXIT \ do { goto leave; } while (0) #define DNS_SM_LEAVE \ leave: (void)0; \ DNS_SM_SAVE_AND_DO(break); \ } \ } while (0) /* * U T I L I T Y R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_MAXINTERVAL 300 struct dns_clock { time_t sample, elapsed; }; /* struct dns_clock */ static void dns_begin(struct dns_clock *clk) { clk->sample = time(0); clk->elapsed = 0; } /* dns_begin() */ static time_t dns_elapsed(struct dns_clock *clk) { time_t curtime; if ((time_t)-1 == time(&curtime)) return clk->elapsed; if (curtime > clk->sample) clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL); clk->sample = curtime; return clk->elapsed; } /* dns_elapsed() */ DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) { size_t n = 0; while (*src++ && n < m) ++n; return n; } /* dns_strnlen() */ DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) { size_t len = dns_strnlen(src, max), n; if (lim > 0) { n = DNS_PP_MIN(lim - 1, len); memcpy(dst, src, n); dst[n] = '\0'; } return len; } /* dns_strnlcpy() */ #if (defined AF_UNIX && !defined _WIN32) #define DNS_HAVE_SOCKADDR_UN 1 #else #define DNS_HAVE_SOCKADDR_UN 0 #endif static size_t dns_af_len(int af) { static const size_t table[AF_MAX] = { [AF_INET6] = sizeof (struct sockaddr_in6), [AF_INET] = sizeof (struct sockaddr_in), #if DNS_HAVE_SOCKADDR_UN [AF_UNIX] = sizeof (struct sockaddr_un), #endif }; return table[af]; } /* dns_af_len() */ #define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family) #define dns_sa_len(sa) dns_af_len(dns_sa_family(sa)) #define DNS_SA_NOPORT &dns_sa_noport static unsigned short dns_sa_noport; static unsigned short *dns_sa_port(int af, void *sa) { switch (af) { case AF_INET6: return &((struct sockaddr_in6 *)sa)->sin6_port; case AF_INET: return &((struct sockaddr_in *)sa)->sin_port; default: return DNS_SA_NOPORT; } } /* dns_sa_port() */ static void *dns_sa_addr(int af, const void *sa, socklen_t *size) { switch (af) { case AF_INET6: { struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr; if (size) *size = sizeof *in6; return in6; } case AF_INET: { struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr; if (size) *size = sizeof *in; return in; } default: if (size) *size = 0; return 0; } } /* dns_sa_addr() */ #if DNS_HAVE_SOCKADDR_UN #define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path) #endif DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) { switch (dns_sa_family(sa)) { #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char *path = ((struct sockaddr_un *)sa)->sun_path; if (size) *size = dns_strnlen(path, DNS_SUNPATHMAX); return path; } #endif default: if (size) *size = 0; return NULL; } } /* dns_sa_path() */ static int dns_sa_cmp(void *a, void *b) { int cmp, af; if ((cmp = dns_sa_family(a) - dns_sa_family(b))) return cmp; switch ((af = dns_sa_family(a))) { case AF_INET: { struct in_addr *a4, *b4; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a4 = dns_sa_addr(af, a, NULL); b4 = dns_sa_addr(af, b, NULL); if (ntohl(a4->s_addr) < ntohl(b4->s_addr)) return -1; if (ntohl(a4->s_addr) > ntohl(b4->s_addr)) return 1; return 0; } case AF_INET6: { struct in6_addr *a6, *b6; size_t i; if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) return cmp; a6 = dns_sa_addr(af, a, NULL); b6 = dns_sa_addr(af, b, NULL); /* XXX: do we need to use in6_clearscope()? */ for (i = 0; i < sizeof a6->s6_addr; i++) { if ((cmp = a6->s6_addr[i] - b6->s6_addr[i])) return cmp; } return 0; } #if DNS_HAVE_SOCKADDR_UN case AF_UNIX: { char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path]; dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX); dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX); return strcmp(a_path, b_path); } #endif default: return -1; } } /* dns_sa_cmp() */ #if _WIN32 static int dns_inet_pton(int af, const void *src, void *dst) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; u.sin.sin_family = af; if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u })) return -1; switch (af) { case AF_INET6: *(struct in6_addr *)dst = u.sin6.sin6_addr; return 1; case AF_INET: *(struct in_addr *)dst = u.sin.sin_addr; return 1; default: return 0; } } /* dns_inet_pton() */ static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) { union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */ memset(&u, 0, sizeof u); u.sin.sin_family = af; switch (af) { case AF_INET6: u.sin6.sin6_addr = *(struct in6_addr *)src; break; case AF_INET: u.sin.sin_addr = *(struct in_addr *)src; break; default: return 0; } if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim)) return 0; return dst; } /* dns_inet_ntop() */ #else #define dns_inet_pton(...) inet_pton(__VA_ARGS__) #define dns_inet_ntop(...) inet_ntop(__VA_ARGS__) #endif static dns_error_t dns_pton(int af, const void *src, void *dst) { switch (dns_inet_pton(af, src, dst)) { case 1: return 0; case -1: return dns_soerr(); default: return DNS_EADDRESS; } } /* dns_pton() */ static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) { return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr(); } /* dns_ntop() */ size_t dns_strlcpy(char *dst, const char *src, size_t lim) { char *d = dst; char *e = &dst[lim]; const char *s = src; if (d < e) { do { if ('\0' == (*d++ = *s++)) return s - src - 1; } while (d < e); d[-1] = '\0'; } while (*s++ != '\0') ;; return s - src - 1; } /* dns_strlcpy() */ size_t dns_strlcat(char *dst, const char *src, size_t lim) { char *d = memchr(dst, '\0', lim); char *e = &dst[lim]; const char *s = src; const char *p; if (d && d < e) { do { if ('\0' == (*d++ = *s++)) return d - dst - 1; } while (d < e); d[-1] = '\0'; } p = s; while (*s++ != '\0') ;; return lim + (s - p - 1); } /* dns_strlcat() */ static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) { void *rp; if (nmemb > 0 && SIZE_MAX / nmemb < size) { *error = EOVERFLOW; return NULL; } if (!(rp = realloc(p, nmemb * size))) *error = (errno)? errno : EINVAL; return rp; } /* dns_reallocarray() */ #if _WIN32 static char *dns_strsep(char **sp, const char *delim) { char *p; if (!(p = *sp)) return 0; *sp += strcspn(p, delim); if (**sp != '\0') { **sp = '\0'; ++*sp; } else *sp = NULL; return p; } /* dns_strsep() */ #else #define dns_strsep(...) strsep(__VA_ARGS__) #endif #if _WIN32 #define strcasecmp(...) _stricmp(__VA_ARGS__) #define strncasecmp(...) _strnicmp(__VA_ARGS__) #endif static inline _Bool dns_isalpha(unsigned char c) { return isalpha(c); } /* dns_isalpha() */ static inline _Bool dns_isdigit(unsigned char c) { return isdigit(c); } /* dns_isdigit() */ static inline _Bool dns_isalnum(unsigned char c) { return isalnum(c); } /* dns_isalnum() */ static inline _Bool dns_isspace(unsigned char c) { return isspace(c); } /* dns_isspace() */ static inline _Bool dns_isgraph(unsigned char c) { return isgraph(c); } /* dns_isgraph() */ static int dns_poll(int fd, short events, int timeout) { fd_set rset, wset; if (!events) return 0; if (fd < 0 || (unsigned)fd >= FD_SETSIZE) return EINVAL; FD_ZERO(&rset); FD_ZERO(&wset); if (events & DNS_POLLIN) FD_SET(fd, &rset); if (events & DNS_POLLOUT) FD_SET(fd, &wset); select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL); return 0; } /* dns_poll() */ #if !_WIN32 DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) { #if DNS_THREAD_SAFE return pthread_sigmask(how, set, oset); #else return (0 == sigprocmask(how, set, oset))? 0 : errno; #endif } /* dns_sigmask() */ #endif static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) { long n = send(fd, src, len, flags); if (n < 0) { *error = dns_soerr(); return 0; } else { *error = 0; return n; } } /* dns_send() */ static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) { long n = recv(fd, dst, lim, flags); if (n < 0) { *error = dns_soerr(); return 0; } else if (n == 0) { *error = (lim > 0)? DNS_ECONNFIN : EINVAL; return 0; } else { *error = 0; return n; } } /* dns_recv() */ static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) { #if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE return dns_send(fd, src, len, flags, _error); #elif defined MSG_NOSIGNAL return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error); #elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */ /* * SIGPIPE handling similar to the approach described in * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html */ sigset_t pending, blocked, piped; size_t count; int error; sigemptyset(&pending); sigpending(&pending); if (!sigismember(&pending, SIGPIPE)) { sigemptyset(&piped); sigaddset(&piped, SIGPIPE); sigemptyset(&blocked); if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked))) goto error; } count = dns_send(fd, src, len, flags, &error); if (!sigismember(&pending, SIGPIPE)) { int saved = error; if (!count && error == EPIPE) { while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR) ;; } if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL))) goto error; error = saved; } *_error = error; return count; error: *_error = error; return 0; #else #error "unable to suppress SIGPIPE" return dns_send(fd, src, len, flags, _error); #endif } /* dns_send_nopipe() */ static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { if (0 != connect(fd, addr, addrlen)) return dns_soerr(); return 0; } /* dns_connect() */ #define DNS_FOPEN_STDFLAGS "rwabt+" static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) { char *p = dst, *pe = dst + lim; /* copy standard flags */ while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) { if (!(p < pe)) return ENOMEM; *p++ = *src++; } /* append flag to standard flags */ if (!(p < pe)) return ENOMEM; *p++ = fc; /* copy remaining mode string, including '\0' */ do { if (!(p < pe)) return ENOMEM; } while ((*p++ = *src++)); return 0; } /* dns_fopen_addflag() */ static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) { FILE *fp; char mode_cloexec[32]; int error; assert(path && mode && *mode); if (!*path) { error = EINVAL; goto error; } #if _WIN32 || _WIN64 if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N'))) goto error; if (!(fp = fopen(path, mode_cloexec))) goto syerr; #else if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e'))) goto error; if (!(fp = fopen(path, mode_cloexec))) { if (errno != EINVAL) goto syerr; if (!(fp = fopen(path, mode))) goto syerr; } #endif return fp; syerr: error = dns_syerr(); error: *_error = error; return NULL; } /* dns_fopen() */ struct dns_hxd_lines_i { int pc; size_t p; }; #define DNS_SM_RESTORE \ do { \ pc = state->pc; \ sp = src + state->p; \ se = src + len; \ } while (0) #define DNS_SM_SAVE \ do { \ state->p = sp - src; \ state->pc = pc; \ } while (0) static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) { static const unsigned char hex[] = "0123456789abcdef"; static const unsigned char tmpl[] = " | |\n"; unsigned char ln[sizeof tmpl]; const unsigned char *sp, *se; unsigned char *h, *g; unsigned i, n; int pc; DNS_SM_ENTER; while (sp < se) { memcpy(ln, tmpl, sizeof ln); h = &ln[2]; g = &ln[53]; for (n = 0; n < 2; n++) { for (i = 0; i < 8 && se - sp > 0; i++, sp++) { h[0] = hex[0x0f & (*sp >> 4)]; h[1] = hex[0x0f & (*sp >> 0)]; h += 3; *g++ = (dns_isgraph(*sp))? *sp : '.'; } h++; } n = dns_strlcpy(dst, (char *)ln, lim); DNS_SM_YIELD(n); } DNS_SM_EXIT; DNS_SM_LEAVE; return 0; } #undef DNS_SM_SAVE #undef DNS_SM_RESTORE /* * A R I T H M E T I C R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_CHECK_OVERFLOW(error, r, f, ...) \ do { \ uintmax_t _r; \ *(error) = f(&_r, __VA_ARGS__); \ *(r) = _r; \ } while (0) static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) { if (n > clamp) { *r = clamp; return ERANGE; } else { *r = n; return 0; } } /* dns_clamp_overflow() */ static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (~a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a + b, clamp); } } /* dns_add_overflow() */ static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { if (a > 0 && UINTMAX_MAX / a < b) { *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); return ERANGE; } else { return dns_clamp_overflow(r, a * b, clamp); } } /* dns_mul_overflow() */ /* * F I X E D - S I Z E D B U F F E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_B_INIT(src, n) { \ (unsigned char *)(src), \ (unsigned char *)(src), \ (unsigned char *)(src) + (n), \ } #define DNS_B_FROM(src, n) DNS_B_INIT((src), (n)) #define DNS_B_INTO(src, n) DNS_B_INIT((src), (n)) struct dns_buf { const unsigned char *base; unsigned char *p; const unsigned char *pe; dns_error_t error; size_t overflow; }; /* struct dns_buf */ static inline size_t dns_b_tell(struct dns_buf *b) { return b->p - b->base; } static inline dns_error_t dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error) { b->overflow += n; return b->error = error; } DNS_NOTUSED static struct dns_buf * dns_b_into(struct dns_buf *b, void *src, size_t n) { *b = (struct dns_buf)DNS_B_INTO(src, n); return b; } static dns_error_t dns_b_putc(struct dns_buf *b, unsigned char uc) { if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_ENOBUFS); *b->p++ = uc; return 0; } static dns_error_t dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p) { size_t pe = b->pe - b->base; if (pe <= p) return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS); *((unsigned char *)b->base + p) = uc; return 0; } static inline dns_error_t dns_b_put16(struct dns_buf *b, uint16_t u) { return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static inline dns_error_t dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p) { if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1)) return b->error; return 0; } DNS_NOTUSED static inline dns_error_t dns_b_put32(struct dns_buf *b, uint32_t u) { return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16), dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); } static dns_error_t dns_b_put(struct dns_buf *b, const void *src, size_t len) { size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len); memcpy(b->p, src, n); b->p += n; if (n < len) return dns_b_setoverflow(b, len - n, DNS_ENOBUFS); return 0; } static dns_error_t dns_b_puts(struct dns_buf *b, const void *src) { return dns_b_put(b, src, strlen(src)); } DNS_NOTUSED static inline dns_error_t dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width) { size_t digits, padding, overflow; uintmax_t r; unsigned char *tp, *te, tc; digits = 0; r = u; do { digits++; r /= 10; } while (r); padding = width - DNS_PP_MIN(digits, width); overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding)); while (padding--) { dns_b_putc(b, '0'); } digits = 0; tp = b->p; r = u; do { if (overflow < ++digits) dns_b_putc(b, '0' + (r % 10)); r /= 10; } while (r); te = b->p; while (tp < te) { tc = *--te; *te = *tp; *tp++ = tc; } return b->error; } static void dns_b_popc(struct dns_buf *b) { if (b->overflow && !--b->overflow) b->error = 0; if (b->p > b->base) b->p--; } static inline const char * dns_b_tolstring(struct dns_buf *b, size_t *n) { if (b->p < b->pe) { *b->p = '\0'; *n = b->p - b->base; return (const char *)b->base; } else if (b->p > b->base) { if (b->p[-1] != '\0') { dns_b_setoverflow(b, 1, DNS_ENOBUFS); b->p[-1] = '\0'; } *n = &b->p[-1] - b->base; return (const char *)b->base; } else { *n = 0; return ""; } } static inline const char * dns_b_tostring(struct dns_buf *b) { size_t n; return dns_b_tolstring(b, &n); } static inline size_t dns_b_strlen(struct dns_buf *b) { size_t n; dns_b_tolstring(b, &n); return n; } static inline size_t dns_b_strllen(struct dns_buf *b) { size_t n = dns_b_strlen(b); return n + b->overflow; } DNS_NOTUSED static const struct dns_buf * dns_b_from(const struct dns_buf *b, const void *src, size_t n) { *(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n); return b; } static inline int dns_b_getc(const struct dns_buf *_b, const int eof) { struct dns_buf *b = (struct dns_buf *)_b; if (!(b->p < b->pe)) return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof; return *b->p++; } static inline intmax_t dns_b_get16(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_getc(b, 0) << 8); n |= (dns_b_getc(b, 0) << 0); return (!b->overflow)? n : eof; } DNS_NOTUSED static inline intmax_t dns_b_get32(const struct dns_buf *b, const intmax_t eof) { intmax_t n; n = (dns_b_get16(b, 0) << 16); n |= (dns_b_get16(b, 0) << 0); return (!b->overflow)? n : eof; } static inline dns_error_t dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n) { struct dns_buf *src = (struct dns_buf *)_src; size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n); size_t src_r = n - src_n; dns_b_put(dst, src->p, src_n); src->p += src_n; if (src_r) return dns_b_setoverflow(src, src_r, DNS_EILLEGAL); return dst->error; } /* * T I M E R O U T I N E S * * Most functions still rely on the older time routines defined in the * utility routines section, above. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_TIME_C(n) UINT64_C(n) #define DNS_TIME_INF (~DNS_TIME_C(0)) typedef uint64_t dns_time_t; typedef dns_time_t dns_microseconds_t; static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) { int error; DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF); return error; } static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) { if (a < b) { *r = DNS_TIME_C(0); return ERANGE; } else { *r = a - b; return 0; } } static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) { if (ts) { dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec); dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec); dns_time_t usec = nsec / 1000; dns_microseconds_t r; if (rup && nsec % 1000 > 0) usec++; dns_time_mul(&r, sec, DNS_TIME_C(1000000)); dns_time_add(&r, r, usec); return r; } else { return DNS_TIME_INF; } } /* dns_ts2us() */ static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) { if (tv) { ts->tv_sec = tv->tv_sec; ts->tv_nsec = tv->tv_usec * 1000; return ts; } else { return NULL; } } /* dns_tv2ts() */ static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, us / 1000000, 1); dns_b_putc(&dst, '.'); dns_b_fmtju(&dst, us % 1000000, 6); return dns_b_strllen(&dst); } /* dns_utime_print() */ /* * P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ unsigned dns_p_count(struct dns_packet *P, enum dns_section section) { unsigned count; switch (section) { case DNS_S_QD: return ntohs(dns_header(P)->qdcount); case DNS_S_AN: return ntohs(dns_header(P)->ancount); case DNS_S_NS: return ntohs(dns_header(P)->nscount); case DNS_S_AR: return ntohs(dns_header(P)->arcount); default: count = 0; if (section & DNS_S_QD) count += ntohs(dns_header(P)->qdcount); if (section & DNS_S_AN) count += ntohs(dns_header(P)->ancount); if (section & DNS_S_NS) count += ntohs(dns_header(P)->nscount); if (section & DNS_S_AR) count += ntohs(dns_header(P)->arcount); return count; } } /* dns_p_count() */ struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) { if (!P) return 0; assert(size >= offsetof(struct dns_packet, data) + 12); memset(P, 0, sizeof *P); P->size = size - offsetof(struct dns_packet, data); P->end = 12; memset(P->data, '\0', 12); return P; } /* dns_p_init() */ static struct dns_packet *dns_p_reset(struct dns_packet *P) { return dns_p_init(P, offsetof(struct dns_packet, data) + P->size); } /* dns_p_reset() */ static unsigned short dns_p_qend(struct dns_packet *P) { unsigned short qend = 12; unsigned i, count = dns_p_count(P, DNS_S_QD); for (i = 0; i < count && qend < P->end; i++) { if (P->end == (qend = dns_d_skip(qend, P))) goto invalid; if (P->end - qend < 4) goto invalid; qend += 4; } return DNS_PP_MIN(qend, P->end); invalid: return P->end; } /* dns_p_qend() */ struct dns_packet *dns_p_make(size_t len, int *error) { struct dns_packet *P; size_t size = dns_p_calcsize(len); if (!(P = dns_p_init(malloc(size), size))) *error = dns_syerr(); return P; } /* dns_p_make() */ static void dns_p_free(struct dns_packet *P) { free(P); } /* dns_p_free() */ /* convience routine to free any existing packet before storing new packet */ static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { dns_p_free(*dst); *dst = src; return src; } /* dns_p_setptr() */ static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) { dns_p_setptr(dst, *src); *src = NULL; return *dst; } /* dns_p_movptr() */ int dns_p_grow(struct dns_packet **P) { struct dns_packet *tmp; size_t size; int error; if (!*P) { if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error))) return error; return 0; } size = dns_p_sizeof(*P); size |= size >> 1; size |= size >> 2; size |= size >> 4; size |= size >> 8; size++; if (size > 65536) return DNS_ENOBUFS; if (!(tmp = realloc(*P, dns_p_calcsize(size)))) return dns_syerr(); tmp->size = size; *P = tmp; return 0; } /* dns_p_grow() */ struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) { if (!P) return 0; P->end = DNS_PP_MIN(P->size, P0->end); memcpy(P->data, P0->data, P->end); return P; } /* dns_p_copy() */ struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) { size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0)); struct dns_packet *M; enum dns_section section; struct dns_rr rr, mr; int error, copy; if (!A && B) { A = B; Amask = Bmask; B = 0; } merge: if (!(M = dns_p_make(bufsiz, &error))) goto error; for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) { if (A && (section & Amask)) { dns_rr_foreach(&rr, A, .section = section) { if ((error = dns_rr_copy(M, &rr, A))) goto error; } } if (B && (section & Bmask)) { dns_rr_foreach(&rr, B, .section = section) { copy = 1; dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) { if (!(copy = dns_rr_cmp(&rr, B, &mr, M))) break; } if (copy && (error = dns_rr_copy(M, &rr, B))) goto error; } } } return M; error: dns_p_setptr(&M, NULL); if (error == DNS_ENOBUFS && bufsiz < 65535) { bufsiz = DNS_PP_MIN(65535, bufsiz * 2); goto merge; } *error_ = error; return 0; } /* dns_p_merge() */ static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t); void dns_p_dictadd(struct dns_packet *P, unsigned short dn) { unsigned short lp, lptr, i; lp = dn; while (lp < P->end) { if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) { lptr = ((0x3f & P->data[lp + 0]) << 8) | ((0xff & P->data[lp + 1]) << 0); for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { if (P->dict[i] == lptr) { P->dict[i] = dn; return; } } } lp = dns_l_skip(lp, P->data, P->end); } for (i = 0; i < lengthof(P->dict); i++) { if (!P->dict[i]) { P->dict[i] = dn; break; } } } /* dns_p_dictadd() */ static inline uint16_t plus1_ns (uint16_t count_net) { uint16_t count = ntohs (count_net); count++; return htons (count); } int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) { size_t end = P->end; int error; if ((error = dns_d_push(P, dn, dnlen))) goto error; if (P->size - P->end < 4) goto nobufs; P->data[P->end++] = 0xff & (type >> 8); P->data[P->end++] = 0xff & (type >> 0); P->data[P->end++] = 0xff & (class >> 8); P->data[P->end++] = 0xff & (class >> 0); if (section == DNS_S_QD) goto update; if (P->size - P->end < 6) goto nobufs; if (type != DNS_T_OPT) ttl = DNS_PP_MIN(ttl, 0x7fffffffU); P->data[P->end++] = ttl >> 24; P->data[P->end++] = ttl >> 16; P->data[P->end++] = ttl >> 8; P->data[P->end++] = ttl >> 0; if ((error = dns_any_push(P, (union dns_any *)any, type))) goto error; update: switch (section) { case DNS_S_QD: if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.qd.base && (error = dns_p_study(P))) goto error; dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount); P->memo.qd.end = P->end; P->memo.an.base = P->end; P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AN: if (dns_p_count(P, DNS_S_NS|DNS_S_AR)) goto order; if (!P->memo.an.base && (error = dns_p_study(P))) goto error; dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount); P->memo.an.end = P->end; P->memo.ns.base = P->end; P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_NS: if (dns_p_count(P, DNS_S_AR)) goto order; if (!P->memo.ns.base && (error = dns_p_study(P))) goto error; dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount); P->memo.ns.end = P->end; P->memo.ar.base = P->end; P->memo.ar.end = P->end; break; case DNS_S_AR: if (!P->memo.ar.base && (error = dns_p_study(P))) goto error; dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount); P->memo.ar.end = P->end; if (type == DNS_T_OPT && !P->memo.opt.p) { P->memo.opt.p = end; P->memo.opt.maxudp = class; P->memo.opt.ttl = ttl; } break; default: error = DNS_ESECTION; goto error; } /* switch() */ return 0; nobufs: error = DNS_ENOBUFS; goto error; order: error = DNS_EORDER; goto error; error: P->end = end; return error; } /* dns_p_push() */ #define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0) #define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0) struct dns_p_lines_i { int pc; enum dns_section section; struct dns_rr rr; int error; }; static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) { va_list ap; int error = 0, n; va_start(ap, fmt); if ((n = vsnprintf(dst, lim, fmt, ap)) < 0) error = errno; va_end(ap); *_error = error; return DNS_PP_MAX(n, 0); } /* dns_p_lines_fmt() */ #define DNS_P_LINE(...) \ do { \ len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \ if (len == 0 && error) \ goto error; \ DNS_SM_YIELD(len); \ } while (0) static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) { int error, pc; size_t len; *_error = 0; DNS_SM_ENTER; DNS_P_LINE(";; [HEADER]\n"); DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid)); DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); while (dns_rr_grep(&state->rr, 1, I, P, &error)) { if (state->section != state->rr.section) { DNS_P_LINE("\n"); DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section), dns_p_count(P, state->rr.section)); } if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error))) goto error; dns_strlcat(dst, "\n", lim); DNS_SM_YIELD(len + 1); state->section = state->rr.section; } if (error) goto error; DNS_SM_EXIT; error: for (;;) { *_error = error; DNS_SM_YIELD(0); } DNS_SM_LEAVE; *_error = 0; return 0; } /* dns_p_lines() */ #undef DNS_P_LINE #undef DNS_SM_SAVE #undef DNS_SM_RESTORE static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { struct dns_p_lines_i lines = { 0 }; char line[sizeof (union dns_any) * 2]; size_t len; int error; while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) { if (len < sizeof line) { fwrite(line, 1, len, fp); } else { fwrite(line, 1, sizeof line - 1, fp); fputc('\n', fp); } } } /* dns_p_dump3() */ void dns_p_dump(struct dns_packet *P, FILE *fp) { dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp); } /* dns_p_dump() */ static void dns_s_unstudy(struct dns_s_memo *m) { m->base = 0; m->end = 0; } static void dns_m_unstudy(struct dns_p_memo *m) { dns_s_unstudy(&m->qd); dns_s_unstudy(&m->an); dns_s_unstudy(&m->ns); dns_s_unstudy(&m->ar); m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; } /* dns_m_unstudy() */ static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) { unsigned short count, rp; count = dns_p_count(P, section); for (rp = base; count && rp < P->end; count--) rp = dns_rr_skip(rp, P); m->base = base; m->end = rp; return 0; } /* dns_s_study() */ static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P))) goto error; if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P))) goto error; if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P))) goto error; if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P))) goto error; m->opt.p = 0; m->opt.maxudp = 0; m->opt.ttl = 0; dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) { m->opt.p = rr.dn.p; m->opt.maxudp = rr.class; m->opt.ttl = rr.ttl; break; } return 0; error: dns_m_unstudy(m); return error; } /* dns_m_study() */ int dns_p_study(struct dns_packet *P) { return dns_m_study(&P->memo, P); } /* dns_p_study() */ enum dns_rcode dns_p_rcode(struct dns_packet *P) { return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode); } /* dns_p_rcode() */ /* * Q U E R Y P A C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #define DNS_Q_RD 0x1 /* recursion desired */ #define DNS_Q_EDNS0 0x2 /* include OPT RR */ static dns_error_t dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags) { struct dns_packet *Q = NULL; int error; if (dns_p_movptr(&Q, _Q)) { dns_p_reset(Q); } else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) { goto error; } if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0))) goto error; dns_header(Q)->rd = !!(qflags & DNS_Q_RD); if (qflags & DNS_Q_EDNS0) { struct dns_opt opt = DNS_OPT_INIT(&opt); opt.version = 0; /* RFC 6891 version */ opt.maxudp = 4096; if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt))) goto error; } *_Q = Q; return 0; error: dns_p_free(Q); return error; } static dns_error_t dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags) { return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags); } static dns_error_t dns_q_remake(struct dns_packet **Q, int qflags) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error; assert(Q && *Q); if ((error = dns_rr_parse(&rr, 12, *Q))) return error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error))) return error; if (qlen >= sizeof qname) return DNS_EILLEGAL; return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags); } /* * D O M A I N N A M E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef DNS_D_MAXPTRS #define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */ #endif static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) { unsigned short len; unsigned nptrs = 0; retry: if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; if (lim > 0) { memcpy(dst, &data[src], DNS_PP_MIN(lim, len)); dst[DNS_PP_MIN(lim - 1, len)] = '\0'; } *nxt = src + len; return len; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: if (++nptrs > DNS_D_MAXPTRS) goto invalid; if (end - src < 2) goto invalid; src = ((0x3f & data[src + 0]) << 8) | ((0xff & data[src + 1]) << 0); goto retry; } /* switch() */ /* NOT REACHED */ invalid: *nxt = end; return 0; } /* dns_l_expand() */ static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) { unsigned short len; if (src >= end) goto invalid; switch (0x03 & (data[src] >> 6)) { case 0x00: len = (0x3f & (data[src++])); if (end - src < len) goto invalid; return (len)? src + len : end; case 0x01: goto invalid; case 0x02: goto invalid; case 0x03: return end; } /* switch() */ /* NOT REACHED */ invalid: return end; } /* dns_l_skip() */ static _Bool dns_d_isanchored(const void *_src, size_t len) { const unsigned char *src = _src; return len > 0 && src[len - 1] == '.'; } /* dns_d_isanchored() */ static size_t dns_d_ndots(const void *_src, size_t len) { const unsigned char *p = _src, *pe = p + len; size_t ndots = 0; while ((p = memchr(p, '.', pe - p))) { ndots++; p++; } return ndots; } /* dns_d_ndots() */ static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) { unsigned char *dst = dst_; const unsigned char *src = src_; size_t dp = 0, sp = 0; int lc; /* trim any leading dot(s) */ while (sp < len && src[sp] == '.') sp++; for (lc = 0; sp < len; lc = src[sp++]) { /* trim extra dot(s) */ if (src[sp] == '.' && lc == '.') continue; if (dp < lim) dst[dp] = src[sp]; dp++; } if ((flags & DNS_D_ANCHOR) && lc != '.') { if (dp < lim) dst[dp] = '.'; dp++; } if (lim > 0) dst[DNS_PP_MIN(dp, lim - 1)] = '\0'; return dp; } /* dns_d_trim() */ char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) { if (flags & DNS_D_TRIM) { dns_d_trim(dst, lim, src, len, flags); } if (flags & DNS_D_ANCHOR) { dns_d_anchor(dst, lim, src, len); } else { memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0'; } return dst; } /* dns_d_init() */ size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) { if (len == 0) return 0; memmove(dst, src, DNS_PP_MIN(lim, len)); if (((const char *)src)[len - 1] != '.') { if (len < lim) ((char *)dst)[len] = '.'; len++; } if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_anchor() */ size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) { const char *dot; /* XXX: Skip any leading dot. Handles cleaving root ".". */ if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1))) return 0; len -= dot - (const char *)src; /* XXX: Unless root, skip the label's trailing dot. */ if (len > 1) { src = ++dot; len--; } else src = dot; memmove(dst, src, DNS_PP_MIN(lim, len)); if (lim > 0) ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; return len; } /* dns_d_cleave() */ size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) { struct { unsigned char *b; size_t p, x; } dst, src; unsigned char ch = '.'; dst.b = dst_; dst.p = 0; dst.x = 1; src.b = (unsigned char *)src_; src.p = 0; src.x = 0; while (src.x < len) { ch = src.b[src.x]; if (ch == '.') { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x++; src.p = ++src.x; } else { if (dst.x < lim) dst.b[dst.x] = ch; dst.x++; src.x++; } } /* while() */ if (src.x > src.p) { if (dst.p < lim) dst.b[dst.p] = (0x3f & (src.x - src.p)); dst.p = dst.x; } if (dst.p > 1) { if (dst.p < lim) dst.b[dst.p] = 0x00; dst.p++; } #if 1 if (dst.p < lim) { struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b; unsigned i; a.p = 0; while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) { for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { b.p = P->dict[i]; while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) { a.y = a.x; b.y = b.x; while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) { a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim); b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end); } if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) { dst.b[a.p++] = 0xc0 | (0x3f & (b.p >> 8)); dst.b[a.p++] = (0xff & (b.p >> 0)); /* silence static analyzers */ dns_assume(a.p > 0); return a.p; } b.p = b.x; } /* while() */ } /* for() */ a.p = a.x; } /* while() */ } /* if () */ #endif if (!dst.p) *error = DNS_EILLEGAL; return dst.p; } /* dns_d_comp() */ unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) { unsigned short len; while (src < P->end) { switch (0x03 & (P->data[src] >> 6)) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src++]); if (0 == len) { /* success ==> */ return src; } else if (P->end - src > len) { src += len; break; } else goto invalid; /* NOT REACHED */ case 0x01: /* RESERVED */ goto invalid; case 0x02: /* RESERVED */ goto invalid; case 0x03: /* POINTER */ if (P->end - src < 2) goto invalid; src += 2; /* success ==> */ return src; } /* switch() */ } /* while() */ invalid: return P->end; } /* dns_d_skip() */ #include size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) { size_t dstp = 0; unsigned nptrs = 0; unsigned char len; while (src < P->end) { switch ((0x03 & (P->data[src] >> 6))) { case 0x00: /* FOLLOWS */ len = (0x3f & P->data[src]); if (0 == len) { if (dstp == 0) { if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; } /* NUL terminate */ if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; /* success ==> */ return dstp; } src++; if (P->end - src < len) goto toolong; if (dstp < lim) memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp)); src += len; dstp += len; if (dstp < lim) ((unsigned char *)dst)[dstp] = '.'; dstp++; nptrs = 0; continue; case 0x01: /* RESERVED */ goto reserved; case 0x02: /* RESERVED */ goto reserved; case 0x03: /* POINTER */ if (++nptrs > DNS_D_MAXPTRS) goto toolong; if (P->end - src < 2) goto toolong; src = ((0x3f & P->data[src + 0]) << 8) | ((0xff & P->data[src + 1]) << 0); continue; } /* switch() */ } /* while() */ toolong: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; reserved: *error = DNS_EILLEGAL; if (lim > 0) ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; return 0; } /* dns_d_expand() */ int dns_d_push(struct dns_packet *P, const void *dn, size_t len) { size_t lim = P->size - P->end; unsigned dp = P->end; int error = DNS_EILLEGAL; /* silence compiler */ len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error); if (len == 0) return error; if (len > lim) return DNS_ENOBUFS; P->end += len; dns_p_dictadd(P, dp); return 0; } /* dns_d_push() */ size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) { char host[DNS_D_MAXNAME + 1]; struct dns_rr_i i; struct dns_rr rr; unsigned depth; int error; if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len)) { error = ENAMETOOLONG; goto error; } for (depth = 0; depth < 7; depth++) { dns_rr_i_init(memset(&i, 0, sizeof i), P); i.section = DNS_S_ALL & ~DNS_S_QD; i.name = host; i.type = DNS_T_CNAME; if (!dns_rr_grep(&rr, 1, &i, P, &error)) break; if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P))) goto error; } return dns_strlcpy(dst, host, lim); error: *error_ = error; return 0; } /* dns_d_cname() */ /* * R E S O U R C E R E C O R D R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) { unsigned char dn[DNS_D_MAXNAME + 1]; union dns_any any; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error))) return error; else if (len >= sizeof dn) return DNS_EILLEGAL; if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q))) return error; return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any); } /* dns_rr_copy() */ int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) { unsigned short p = src; if (src >= P->end) goto invalid; rr->dn.p = p; rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p; if (P->end - p < 4) goto invalid; rr->type = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->class = ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); p += 4; if (src < dns_p_qend(P)) { rr->section = DNS_S_QUESTION; rr->ttl = 0; rr->rd.p = 0; rr->rd.len = 0; return 0; } if (P->end - p < 4) goto invalid; rr->ttl = ((0xff & P->data[p + 0]) << 24) | ((0xff & P->data[p + 1]) << 16) | ((0xff & P->data[p + 2]) << 8) | ((0xff & P->data[p + 3]) << 0); if (rr->type != DNS_T_OPT) rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU); p += 4; if (P->end - p < 2) goto invalid; rr->rd.len = ((0xff & P->data[p + 0]) << 8) | ((0xff & P->data[p + 1]) << 0); rr->rd.p = p + 2; p += 2; if (P->end - p < rr->rd.len) goto invalid; return 0; invalid: return DNS_EILLEGAL; } /* dns_rr_parse() */ static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) { unsigned short rp, rdlen; rp = dns_d_skip(src, P); if (P->end - rp < 4) return P->end - src; rp += 4; /* TYPE, CLASS */ if (rp <= dns_p_qend(P)) return rp - src; if (P->end - rp < 6) return P->end - src; rp += 6; /* TTL, RDLEN */ rdlen = ((0xff & P->data[rp - 2]) << 8) | ((0xff & P->data[rp - 1]) << 0); if (P->end - rp < rdlen) return P->end - src; rp += rdlen; return rp - src; } /* dns_rr_len() */ unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) { return src + dns_rr_len(src, P); } /* dns_rr_skip() */ static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) { enum dns_section section; unsigned count, index; unsigned short rp; if (src >= P->memo.qd.base && src < P->memo.qd.end) return DNS_S_QD; if (src >= P->memo.an.base && src < P->memo.an.end) return DNS_S_AN; if (src >= P->memo.ns.base && src < P->memo.ns.end) return DNS_S_NS; if (src >= P->memo.ar.base && src < P->memo.ar.end) return DNS_S_AR; /* NOTE: Possibly bad memoization. Try it the hard-way. */ for (rp = 12, index = 0; rp < src && rp < P->end; index++) rp = dns_rr_skip(rp, P); section = DNS_S_QD; count = dns_p_count(P, section); while (index >= count && section <= DNS_S_AR) { section <<= 1; count += dns_p_count(P, section); } return DNS_S_ALL & section; } /* dns_rr_section() */ static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) { struct dns_rr rr; int error; if ((error = dns_rr_parse(&rr, src, P))) return 0; return rr.type; } /* dns_rr_type() */ int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) { char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1]; union dns_any any0, any1; int cmp, error; size_t len; if ((cmp = r0->type - r1->type)) return cmp; if ((cmp = r0->class - r1->class)) return cmp; /* * FIXME: Do label-by-label comparison to handle illegally long names? */ if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error)) || len >= sizeof host0) return -1; if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error)) || len >= sizeof host1) return 1; if ((cmp = strcasecmp(host0, host1))) return cmp; if (DNS_S_QD & (r0->section | r1->section)) { if (r0->section == r1->section) return 0; return (r0->section == DNS_S_QD)? -1 : 1; } if ((error = dns_any_parse(&any0, r0, P0))) return -1; if ((error = dns_any_parse(&any1, r1, P1))) return 1; return dns_any_cmp(&any0, r0->type, &any1, r1->type); } /* dns_rr_cmp() */ static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) { struct dns_rr rr1; dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) { if (0 == dns_rr_cmp(rr0, P0, &rr1, P1)) return 1; } return 0; } /* dns_rr_exists() */ static unsigned short dns_rr_offset(struct dns_rr *rr) { return rr->dn.p; } /* dns_rr_offset() */ static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) { if (i->section && !(rr->section & i->section)) return 0; if (i->type && rr->type != i->type && i->type != DNS_T_ALL) return 0; if (i->class && rr->class != i->class && i->class != DNS_C_ANY) return 0; if (i->name) { char dn[DNS_D_MAXNAME + 1]; size_t len; int error; if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error)) || len >= sizeof dn) return 0; if (0 != strcasecmp(dn, i->name)) return 0; } if (i->data && i->type && rr->section > DNS_S_QD) { union dns_any rd; int error; if ((error = dns_any_parse(&rd, rr, P))) return 0; if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type)) return 0; } return 1; } /* dns_rr_i_match() */ static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) { unsigned short rp; struct dns_rr r0, rr; int error; if ((i->section & DNS_S_QD) && P->memo.qd.base) rp = P->memo.qd.base; else if ((i->section & DNS_S_AN) && P->memo.an.base) rp = P->memo.an.base; else if ((i->section & DNS_S_NS) && P->memo.ns.base) rp = P->memo.ns.base; else if ((i->section & DNS_S_AR) && P->memo.ar.base) rp = P->memo.ar.base; else rp = 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; r0 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r0); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) < 0) r0 = rr; } return dns_rr_offset(&r0); } /* dns_rr_i_start() */ static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) { struct dns_rr r0, r1, rr; int error; if ((error = dns_rr_parse(&r0, rp, P))) return P->end; r0.section = dns_rr_section(rp, P); rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12; for (; rp < P->end; rp = dns_rr_skip(rp, P)) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; r1 = rr; goto lower; } return P->end; lower: if (i->sort == &dns_rr_i_packet) return dns_rr_offset(&r1); while ((rp = dns_rr_skip(rp, P)) < P->end) { if ((error = dns_rr_parse(&rr, rp, P))) continue; rr.section = dns_rr_section(rp, P); if (!dns_rr_i_match(&rr, i, P)) continue; if (i->sort(&rr, &r0, i, P) <= 0) continue; if (i->sort(&rr, &r1, i, P) >= 0) continue; r1 = rr; } return dns_rr_offset(&r1); } /* dns_rr_i_skip() */ int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { (void)i; (void)P; return (int)a->dn.p - (int)b->dn.p; } /* dns_rr_i_packet() */ int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; if ((cmp = a->section - b->section)) return cmp; if (a->type != b->type) return (int)a->dn.p - (int)b->dn.p; return dns_rr_cmp(a, P, b, P); } /* dns_rr_i_order() */ int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { int cmp; (void)i; (void)P; while (!i->state.regs[0]) i->state.regs[0] = dns_random(); if ((cmp = a->section - b->section)) return cmp; return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]); } /* dns_rr_i_shuffle() */ struct dns_rr_i *dns_rr_i_init(struct dns_rr_i *i, struct dns_packet *P) { static const struct dns_rr_i i_initializer; (void)P; i->state = i_initializer.state; i->saved = i->state; return i; } /* dns_rr_i_init() */ unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) { unsigned count = 0; int error; switch (i->state.exec) { case 0: if (!i->sort) i->sort = &dns_rr_i_packet; i->state.next = dns_rr_i_start(i, P); i->state.exec++; /* FALL THROUGH */ case 1: while (count < lim && i->state.next < P->end) { if ((error = dns_rr_parse(rr, i->state.next, P))) goto error; rr->section = dns_rr_section(i->state.next, P); rr++; count++; i->state.count++; i->state.next = dns_rr_i_skip(i->state.next, i, P); } /* while() */ break; } /* switch() */ return count; error: *error_ = error; return count; } /* dns_rr_grep() */ size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) { struct dns_buf dst = DNS_B_INTO(_dst, lim); union dns_any any; size_t n; int error; if (rr->section == DNS_S_QD) dns_b_putc(&dst, ';'); if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error))) goto error; dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1)); if (rr->section != DNS_S_QD) { dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, rr->ttl, 0); } dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strclass(rr->class)); dns_b_putc(&dst, ' '); dns_b_puts(&dst, dns_strtype(rr->type)); if (rr->section == DNS_S_QD) goto epilog; dns_b_putc(&dst, ' '); if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P))) goto error; n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type); dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p)); epilog: return dns_b_strllen(&dst); error: *_error = error; return 0; } /* dns_rr_print() */ int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) { unsigned long addr; if (rr->rd.len != 4) return DNS_EILLEGAL; addr = ((0xffU & P->data[rr->rd.p + 0]) << 24) | ((0xffU & P->data[rr->rd.p + 1]) << 16) | ((0xffU & P->data[rr->rd.p + 2]) << 8) | ((0xffU & P->data[rr->rd.p + 3]) << 0); a->addr.s_addr = htonl(addr); return 0; } /* dns_a_parse() */ int dns_a_push(struct dns_packet *P, struct dns_a *a) { unsigned long addr; if (P->size - P->end < 6) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x04; addr = ntohl(a->addr.s_addr); P->data[P->end++] = 0xffU & (addr >> 24); P->data[P->end++] = 0xffU & (addr >> 16); P->data[P->end++] = 0xffU & (addr >> 8); P->data[P->end++] = 0xffU & (addr >> 0); return 0; } /* dns_a_push() */ size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned long octets = ntohl(a->addr.s_addr); unsigned i; for (i = 0; i < 4; i++) { dns_b_fmtju(&dst, 0xff & octets, 0); dns_b_putc(&dst, '.'); octets >>= 8; } dns_b_puts(&dst, "in-addr.arpa."); return dns_b_strllen(&dst); } /* dns_a_arpa() */ int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) { if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr)) return -1; if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr)) return 1; return 0; } /* dns_a_cmp() */ size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) { char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0"; dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_a_print() */ int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) { if (rr->rd.len != sizeof aaaa->addr.s6_addr) return DNS_EILLEGAL; memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr); return 0; } /* dns_aaaa_parse() */ int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) { if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr) return DNS_ENOBUFS; P->data[P->end++] = 0x00; P->data[P->end++] = 0x10; memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr); P->end += sizeof aaaa->addr.s6_addr; return 0; } /* dns_aaaa_push() */ int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) { unsigned i; int cmp; for (i = 0; i < lengthof(a->addr.s6_addr); i++) { if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i]))) return cmp; } return 0; } /* dns_aaaa_cmp() */ size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned nyble; int i, j; for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) { nyble = aaaa->addr.s6_addr[i]; for (j = 0; j < 2; j++) { dns_b_putc(&dst, hex[0x0f & nyble]); dns_b_putc(&dst, '.'); nyble >>= 4; } } dns_b_puts(&dst, "ip6.arpa."); return dns_b_strllen(&dst); } /* dns_aaaa_arpa() */ size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) { char addr[INET6_ADDRSTRLEN + 1] = "::"; dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr); return dns_strlcpy(dst, addr, lim); } /* dns_aaaa_print() */ int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (rr->rd.len < 3) return DNS_EILLEGAL; mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8)) | (0x00ff & (P->data[rr->rd.p + 1] << 0)); if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error))) return error; else if (len >= sizeof mx->host) return DNS_EILLEGAL; return 0; } /* dns_mx_parse() */ int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) { size_t end, len; int error; if (P->size - P->end < 5) return DNS_ENOBUFS; end = P->end; P->end += 2; P->data[P->end++] = 0xff & (mx->preference >> 8); P->data[P->end++] = 0xff & (mx->preference >> 0); if ((error = dns_d_push(P, mx->host, strlen(mx->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_mx_push() */ int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) { int cmp; if ((cmp = a->preference - b->preference)) return cmp; return strcasecmp(a->host, b->host); } /* dns_mx_cmp() */ size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, mx->preference, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, mx->host); return dns_b_strllen(&dst); } /* dns_mx_print() */ size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) { return dns_strlcpy(dst, mx->host, lim); } /* dns_mx_cname() */ int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) { size_t len; int error; if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error))) return error; else if (len >= sizeof ns->host) return DNS_EILLEGAL; return 0; } /* dns_ns_parse() */ int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) { size_t end, len; int error; if (P->size - P->end < 3) return DNS_ENOBUFS; end = P->end; P->end += 2; if ((error = dns_d_push(P, ns->host, strlen(ns->host)))) goto error; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; error: P->end = end; return error; } /* dns_ns_push() */ int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) { return strcasecmp(a->host, b->host); } /* dns_ns_cmp() */ size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_print() */ size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) { return dns_strlcpy(dst, ns->host, lim); } /* dns_ns_cname() */ int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)cname, rr, P); } /* dns_cname_parse() */ int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) { return dns_ns_push(P, (struct dns_ns *)cname); } /* dns_cname_push() */ int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) { return strcasecmp(a->host, b->host); } /* dns_cname_cmp() */ size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) { return dns_ns_print(dst, lim, (struct dns_ns *)cname); } /* dns_cname_print() */ size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) { return dns_strlcpy(dst, cname->host, lim); } /* dns_cname_cname() */ int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) { struct { void *dst; size_t lim; } dn[] = { { soa->mname, sizeof soa->mname }, { soa->rname, sizeof soa->rname } }; unsigned *ts[] = { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum }; unsigned short rp; unsigned i, j, n; int error; /* MNAME / RNAME */ if ((rp = rr->rd.p) >= P->end) return DNS_EILLEGAL; for (i = 0; i < lengthof(dn); i++) { if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error))) return error; else if (n >= dn[i].lim) return DNS_EILLEGAL; if ((rp = dns_d_skip(rp, P)) >= P->end) return DNS_EILLEGAL; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { for (j = 0; j < 4; j++, rp++) { if (rp >= P->end) return DNS_EILLEGAL; *ts[i] <<= 8; *ts[i] |= (0xff & P->data[rp]); } } return 0; } /* dns_soa_parse() */ int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) { void *dn[] = { soa->mname, soa->rname }; unsigned ts[] = { (0xffffffff & soa->serial), (0x7fffffff & soa->refresh), (0x7fffffff & soa->retry), (0x7fffffff & soa->expire), (0xffffffff & soa->minimum) }; unsigned i, j; size_t end, len; int error; end = P->end; if ((P->end += 2) >= P->size) goto toolong; /* MNAME / RNAME */ for (i = 0; i < lengthof(dn); i++) { if ((error = dns_d_push(P, dn[i], strlen(dn[i])))) goto error; } /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ for (i = 0; i < lengthof(ts); i++) { if ((P->end += 4) >= P->size) goto toolong; for (j = 1; j <= 4; j++) { P->data[P->end - j] = (0xff & ts[i]); ts[i] >>= 8; } } len = P->end - end - 2; P->data[end + 0] = (0xff & (len >> 8)); P->data[end + 1] = (0xff & (len >> 0)); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_soa_push() */ int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) { int cmp; if ((cmp = strcasecmp(a->mname, b->mname))) return cmp; if ((cmp = strcasecmp(a->rname, b->rname))) return cmp; if (a->serial > b->serial) return -1; else if (a->serial < b->serial) return 1; if (a->refresh > b->refresh) return -1; else if (a->refresh < b->refresh) return 1; if (a->retry > b->retry) return -1; else if (a->retry < b->retry) return 1; if (a->expire > b->expire) return -1; else if (a->expire < b->expire) return 1; if (a->minimum > b->minimum) return -1; else if (a->minimum < b->minimum) return 1; return 0; } /* dns_soa_cmp() */ size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_puts(&dst, soa->mname); dns_b_putc(&dst, ' '); dns_b_puts(&dst, soa->rname); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->serial, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->refresh, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->retry, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->expire, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, soa->minimum, 0); return dns_b_strllen(&dst); } /* dns_soa_print() */ int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) { unsigned short rp; unsigned i; size_t n; int error; memset(srv, '\0', sizeof *srv); rp = rr->rd.p; if (rr->rd.len < 7) return DNS_EILLEGAL; for (i = 0; i < 2; i++, rp++) { srv->priority <<= 8; srv->priority |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->weight <<= 8; srv->weight |= (0xff & P->data[rp]); } for (i = 0; i < 2; i++, rp++) { srv->port <<= 8; srv->port |= (0xff & P->data[rp]); } if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error))) return error; else if (n >= sizeof srv->target) return DNS_EILLEGAL; return 0; } /* dns_srv_parse() */ int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) { size_t end, len; int error; end = P->end; if (P->size - P->end < 2) goto toolong; P->end += 2; if (P->size - P->end < 6) goto toolong; P->data[P->end++] = 0xff & (srv->priority >> 8); P->data[P->end++] = 0xff & (srv->priority >> 0); P->data[P->end++] = 0xff & (srv->weight >> 8); P->data[P->end++] = 0xff & (srv->weight >> 0); P->data[P->end++] = 0xff & (srv->port >> 8); P->data[P->end++] = 0xff & (srv->port >> 0); if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error))) goto error; else if (P->size - P->end < len) goto toolong; P->end += len; if (P->end > 65535) goto toolong; len = P->end - end - 2; P->data[end + 0] = 0xff & (len >> 8); P->data[end + 1] = 0xff & (len >> 0); return 0; toolong: error = DNS_ENOBUFS; /* FALL THROUGH */ error: P->end = end; return error; } /* dns_srv_push() */ int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) { int cmp; if ((cmp = a->priority - b->priority)) return cmp; /* * FIXME: We need some sort of random seed to implement the dynamic * weighting required by RFC 2782. */ if ((cmp = a->weight - b->weight)) return cmp; if ((cmp = a->port - b->port)) return cmp; return strcasecmp(a->target, b->target); } /* dns_srv_cmp() */ size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) { struct dns_buf dst = DNS_B_INTO(_dst, lim); dns_b_fmtju(&dst, srv->priority, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->weight, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, srv->port, 0); dns_b_putc(&dst, ' '); dns_b_puts(&dst, srv->target); return dns_b_strllen(&dst); } /* dns_srv_print() */ size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) { return dns_strlcpy(dst, srv->target, lim); } /* dns_srv_cname() */ unsigned int dns_opt_ttl(const struct dns_opt *opt) { unsigned int ttl = 0; ttl |= (0xffU & opt->rcode) << 24; ttl |= (0xffU & opt->version) << 16; ttl |= (0xffffU & opt->flags) << 0; return ttl; } /* dns_opt_ttl() */ unsigned short dns_opt_class(const struct dns_opt *opt) { return opt->maxudp; } /* dns_opt_class() */ struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) { assert(size >= offsetof(struct dns_opt, data)); opt->size = size - offsetof(struct dns_opt, data); opt->len = 0; opt->rcode = 0; opt->version = 0; opt->maxudp = 0; return opt; } /* dns_opt_init() */ static union dns_any *dns_opt_initany(union dns_any *any, size_t size) { return dns_opt_init(&any->opt, size), any; } /* dns_opt_initany() */ int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) { const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len); struct dns_buf dst = DNS_B_INTO(opt->data, opt->size); int error; opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode); opt->version = 0xff & (rr->ttl >> 16); opt->flags = 0xffff & rr->ttl; opt->maxudp = 0xffff & rr->class; while (src.p < src.pe) { int code, len; if (-1 == (code = dns_b_get16(&src, -1))) return src.error; if (-1 == (len = dns_b_get16(&src, -1))) return src.error; switch (code) { default: dns_b_put16(&dst, code); dns_b_put16(&dst, len); if ((error = dns_b_move(&dst, &src, len))) return error; break; } } return 0; } /* dns_opt_parse() */ int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) { const struct dns_buf src = DNS_B_FROM(opt->data, opt->len); struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end)); int error; /* rdata length (see below) */ if ((error = dns_b_put16(&dst, 0))) goto error; /* ... push known options here */ /* push opaque option data */ if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p)))) goto error; /* rdata length */ if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0))) goto error; #if !DNS_DEBUG_OPT_FORMERR P->end += dns_b_tell(&dst); #endif return 0; error: return error; } /* dns_opt_push() */ int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) { (void)a; (void)b; return -1; } /* dns_opt_cmp() */ size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) { struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t p; dns_b_putc(&dst, '"'); for (p = 0; p < opt->len; p++) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, opt->data[p], 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_opt_print() */ int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) { return dns_ns_parse((struct dns_ns *)ptr, rr, P); } /* dns_ptr_parse() */ int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) { return dns_ns_push(P, (struct dns_ns *)ptr); } /* dns_ptr_push() */ size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) { switch (af) { case AF_INET6: return dns_aaaa_arpa(dst, lim, addr); case AF_INET: return dns_a_arpa(dst, lim, addr); default: { struct dns_a a; a.addr.s_addr = INADDR_NONE; return dns_a_arpa(dst, lim, &a); } } } /* dns_ptr_qname() */ int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) { return strcasecmp(a->host, b->host); } /* dns_ptr_cmp() */ size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_ns_print(dst, lim, (struct dns_ns *)ptr); } /* dns_ptr_print() */ size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) { return dns_strlcpy(dst, ptr->host, lim); } /* dns_ptr_cname() */ int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) { unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len; if (pe - p < 2) return DNS_EILLEGAL; fp->algo = P->data[p++]; fp->type = P->data[p++]; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_EILLEGAL; memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1); break; default: break; } /* switch() */ return 0; } /* dns_sshfp_parse() */ int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) { unsigned p = P->end, pe = P->size, n; if (pe - p < 4) return DNS_ENOBUFS; p += 2; P->data[p++] = 0xff & fp->algo; P->data[p++] = 0xff & fp->type; switch (fp->type) { case DNS_SSHFP_SHA1: if (pe - p < sizeof fp->digest.sha1) return DNS_ENOBUFS; memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1); p += sizeof fp->digest.sha1; break; default: return DNS_EILLEGAL; } /* switch() */ n = p - P->end - 2; P->data[P->end++] = 0xff & (n >> 8); P->data[P->end++] = 0xff & (n >> 0); P->end = p; return 0; } /* dns_sshfp_push() */ int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) { int cmp; if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type)) return cmp; switch (a->type) { case DNS_SSHFP_SHA1: return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1); default: return 0; } /* switch() */ /* NOT REACHED */ } /* dns_sshfp_cmp() */ size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) { static const unsigned char hex[16] = "0123456789abcdef"; struct dns_buf dst = DNS_B_INTO(_dst, lim); size_t i; dns_b_fmtju(&dst, fp->algo, 0); dns_b_putc(&dst, ' '); dns_b_fmtju(&dst, fp->type, 0); dns_b_putc(&dst, ' '); switch (fp->type) { case DNS_SSHFP_SHA1: for (i = 0; i < sizeof fp->digest.sha1; i++) { dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]); dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]); } break; default: dns_b_putc(&dst, '0'); break; } /* switch() */ return dns_b_strllen(&dst); } /* dns_sshfp_print() */ struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) { assert(size > offsetof(struct dns_txt, data)); txt->size = size - offsetof(struct dns_txt, data); txt->len = 0; return txt; } /* dns_txt_init() */ static union dns_any *dns_txt_initany(union dns_any *any, size_t size) { /* NB: union dns_any is already initialized as struct dns_txt */ (void)size; return any; } /* dns_txt_initany() */ int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = txt->data; dst.p = 0; dst.end = txt->size; src.b = P->data; src.p = rr->rd.p; src.end = src.p + rr->rd.len; while (src.p < src.end) { n = 0xff & P->data[src.p++]; if (src.end - src.p < n || dst.end - dst.p < n) return DNS_EILLEGAL; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } txt->len = dst.p; return 0; } /* dns_txt_parse() */ int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) { struct { unsigned char *b; size_t p, end; } dst, src; unsigned n; dst.b = P->data; dst.p = P->end; dst.end = P->size; src.b = txt->data; src.p = 0; src.end = txt->len; if (dst.end - dst.p < 2) return DNS_ENOBUFS; n = txt->len + ((txt->len + 254) / 255); dst.b[dst.p++] = 0xff & (n >> 8); dst.b[dst.p++] = 0xff & (n >> 0); while (src.p < src.end) { n = DNS_PP_MIN(255, src.end - src.p); if (dst.p >= dst.end) return DNS_ENOBUFS; dst.b[dst.p++] = n; if (dst.end - dst.p < n) return DNS_ENOBUFS; memcpy(&dst.b[dst.p], &src.b[src.p], n); dst.p += n; src.p += n; } P->end = dst.p; return 0; } /* dns_txt_push() */ int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) { (void)a; (void)b; return -1; } /* dns_txt_cmp() */ size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) { struct dns_buf src = DNS_B_FROM(txt->data, txt->len); struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; if (src.p < src.pe) { do { dns_b_putc(&dst, '"'); for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) { if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p, 3); } else { dns_b_putc(&dst, *src.p); } } dns_b_putc(&dst, '"'); dns_b_putc(&dst, ' '); } while (src.p < src.pe); dns_b_popc(&dst); } else { dns_b_putc(&dst, '"'); dns_b_putc(&dst, '"'); } return dns_b_strllen(&dst); } /* dns_txt_print() */ /* Some of the function pointers of DNS_RRTYPES are initialized with * slighlly different functions, thus we can't use prototypes. */ DNS_PRAGMA_PUSH #if __clang__ #pragma clang diagnostic ignored "-Wstrict-prototypes" #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic ignored "-Wstrict-prototypes" #endif static const struct dns_rrtype { enum dns_type type; const char *name; union dns_any *(*init)(union dns_any *, size_t); int (*parse)(); int (*push)(); int (*cmp)(); size_t (*print)(); size_t (*cname)(); } dns_rrtypes[] = { { DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, }, { DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, }, { DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, }, { DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, }, { DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, }, { DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, }, { DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, }, { DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, }, { DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, }, { DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, { DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, }, { DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, }, }; /* dns_rrtypes[] */ DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/ static const struct dns_rrtype *dns_rrtype(enum dns_type type) { const struct dns_rrtype *t; for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) { if (t->type == type && t->parse) { return t; } } return NULL; } /* dns_rrtype() */ union dns_any *dns_any_init(union dns_any *any, size_t size) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return (union dns_any *)dns_txt_init(&any->rdata, size); } /* dns_any_init() */ static size_t dns_any_sizeof(union dns_any *any) { dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); return offsetof(struct dns_txt, data) + any->rdata.size; } /* dns_any_sizeof() */ static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) { return (t->init)? t->init(any, dns_any_sizeof(any)) : any; } /* dns_any_reinit() */ int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) { const struct dns_rrtype *t; if ((t = dns_rrtype(rr->type))) return t->parse(dns_any_reinit(any, t), rr, P); if (rr->rd.len > any->rdata.size) return DNS_EILLEGAL; memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len); any->rdata.len = rr->rd.len; return 0; } /* dns_any_parse() */ int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type))) return t->push(P, any); if (P->size - P->end < any->rdata.len + 2) return DNS_ENOBUFS; P->data[P->end++] = 0xff & (any->rdata.len >> 8); P->data[P->end++] = 0xff & (any->rdata.len >> 0); memcpy(&P->data[P->end], any->rdata.data, any->rdata.len); P->end += any->rdata.len; return 0; } /* dns_any_push() */ int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) { const struct dns_rrtype *t; int cmp; if ((cmp = x - y)) return cmp; if ((t = dns_rrtype(x))) return t->cmp(a, b); return -1; } /* dns_any_cmp() */ size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; struct dns_buf src, dst; if ((t = dns_rrtype(type))) return t->print(_dst, lim, any); dns_b_from(&src, any->rdata.data, any->rdata.len); dns_b_into(&dst, _dst, lim); dns_b_putc(&dst, '"'); while (src.p < src.pe) { dns_b_putc(&dst, '\\'); dns_b_fmtju(&dst, *src.p++, 3); } dns_b_putc(&dst, '"'); return dns_b_strllen(&dst); } /* dns_any_print() */ size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) { const struct dns_rrtype *t; if ((t = dns_rrtype(type)) && t->cname) return t->cname(dst, lim, any); return 0; } /* dns_any_cname() */ /* * E V E N T T R A C I N G R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include /* DBL_MANT_DIG */ #include /* PRIu64 */ /* for default trace ID generation try to fit in lua_Number, usually double */ #define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */ #define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1))) #define DNS_TRACE_ID_PRI PRIu64 static inline dns_trace_id_t dns_trace_mkid(void) { dns_trace_id_t id = 0; unsigned r; /* return type of dns_random() */ const size_t id_bit = sizeof id * CHAR_BIT; const size_t r_bit = sizeof r * CHAR_BIT; for (size_t n = 0; n < id_bit; n += r_bit) { r = dns_random(); id <<= r_bit; id |= r; } return DNS_TRACE_ID_MASK & id; } struct dns_trace { dns_atomic_t refcount; FILE *fp; dns_trace_id_t id; struct { struct dns_trace_cname { char host[DNS_D_MAXNAME + 1]; struct sockaddr_storage addr; } base[4]; size_t p; } cnames; }; static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) { socklen_t n = sizeof *ss; if (0 != f(fd, (struct sockaddr *)ss, &n)) goto unspec; if (n > sizeof *ss) goto unspec; return; unspec: memset(ss, '\0', sizeof *ss); ss->ss_family = AF_UNSPEC; } static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) { dns_te_initname(local, fd, &getsockname); dns_te_initname(remote, fd, &getpeername); } static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) { /* NB: silence valgrind */ memset(te, '\0', offsetof(struct dns_trace_event, data)); te->type = type; return te; } int dns_trace_abi(void) { return DNS_TRACE_ABI; } struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) { static const struct dns_trace trace_initializer = { .refcount = 1 }; struct dns_trace *trace; if (!(trace = malloc(sizeof *trace))) goto syerr; *trace = trace_initializer; if (fp) { trace->fp = fp; } else if (!(fp = tmpfile())) { goto syerr; } trace->id = dns_trace_mkid(); return trace; syerr: *error = dns_syerr(); dns_trace_close(trace); return NULL; } /* dns_trace_open() */ void dns_trace_close(struct dns_trace *trace) { if (!trace || 1 != dns_trace_release(trace)) return; if (trace->fp) fclose(trace->fp); free(trace); } /* dns_trace_close() */ dns_refcount_t dns_trace_acquire(struct dns_trace *trace) { return dns_atomic_fetch_add(&trace->refcount); } /* dns_trace_acquire() */ static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) { return (trace)? dns_trace_acquire(trace), trace : NULL; } /* dns_trace_acquire_p() */ dns_refcount_t dns_trace_release(struct dns_trace *trace) { return dns_atomic_fetch_sub(&trace->refcount); } /* dns_trace_release() */ dns_trace_id_t dns_trace_id(struct dns_trace *trace) { return trace->id; } /* dns_trace_id() */ dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) { trace->id = (id)? id : dns_trace_mkid(); return trace->id; } /* dns_trace_setid() */ struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) { return dns_trace_fget(tp, trace->fp, error); } /* dns_trace_get() */ dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_fput(te, data, datasize, trace->fp); } /* dns_trace_put() */ struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) { struct timeval tv; te->id = trace->id; gettimeofday(&tv, NULL); dns_tv2ts(&te->ts, &tv); te->abi = DNS_TRACE_ABI; return te; } /* dns_trace_tag() */ static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) { return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize); } /* dns_trace_tag_and_put() */ struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) { const size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp, *te; size_t n; errno = 0; if (!(n = fread(&tmp, 1, headsize, fp))) goto none; if (n < offsetof(struct dns_trace_event, data)) goto some; if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) { *error = errno; return NULL; } *tp = te; memcpy(te, &tmp, offsetof(struct dns_trace_event, data)); if (dns_te_datasize(te)) { errno = 0; if (!(n = fread(te->data, 1, dns_te_datasize(te), fp))) goto none; if (n < dns_te_datasize(te)) goto some; } return te; none: *error = (ferror(fp))? errno : 0; return NULL; some: *error = 0; return NULL; } dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) { size_t headsize = offsetof(struct dns_trace_event, data); struct dns_trace_event tmp; memcpy(&tmp, te, headsize); tmp.size = headsize + datasize; /* NB: ignore seek error as fp might not point to a regular file */ (void)fseek(fp, 0, SEEK_END); if (fwrite(&tmp, 1, headsize, fp) < headsize) return errno; if (data) if (fwrite(data, 1, datasize, fp) < datasize) return errno; if (fflush(fp)) return errno; return 0; } static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) { struct dns_trace_cname *cname; if (!trace || !trace->fp) return; cname = &trace->cnames.base[trace->cnames.p]; dns_strlcpy(cname->host, host, sizeof cname->host); memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr)); trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base); } static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) { if (!trace || !trace->fp) return NULL; /* NB: start search from the write cursor to */ for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) { if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr)) return cname->host; } return NULL; } static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_SUBMIT); dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname); te.res_submit.qtype = qtype; te.res_submit.qclass = qclass; te.res_submit.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_RES_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.res_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) { struct dns_trace_event te; const char *cname; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_SUBMIT); memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr)); if ((cname = dns_trace_cname(trace, haddr))) dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname); te.so_submit.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_VERIFY); te.so_verify.error = error; dns_trace_tag_and_put(trace, &te, packet->data, packet->end); } static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { struct dns_trace_event te; const void *data; size_t datasize; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SO_FETCH); data = (packet)? packet->data : NULL; datasize = (packet)? packet->end : 0; te.so_fetch.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_CONNECT); dns_te_initname(&te.sys_connect.src, fd, &getsockname); memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst)); te.sys_connect.socktype = socktype; te.sys_connect.error = error; dns_trace_tag_and_put(trace, &te, NULL, 0); } static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_SEND); dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd); te.sys_send.socktype = socktype; te.sys_send.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { struct dns_trace_event te; if (!trace || !trace->fp) return; dns_te_init(&te, DNS_TE_SYS_RECV); dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd); te.sys_recv.socktype = socktype; te.sys_recv.error = error; dns_trace_tag_and_put(trace, &te, data, datasize); } static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_packet *packet = NULL; char *line = NULL, *p; size_t size = 1, skip = 0; struct dns_rr_i records; struct dns_p_lines_i lines; size_t len, count; int error; if (!(packet = dns_p_make(datasize, &error))) goto error; memcpy(packet->data, data, datasize); packet->end = datasize; (void)dns_p_study(packet); resize: if (!(p = dns_reallocarray(line, size, 2, &error))) goto error; line = p; size *= 2; memset(&records, 0, sizeof records); memset(&lines, 0, sizeof lines); count = 0; while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) { if (!(len < size)) { skip = count; goto resize; } else if (skip <= count) { fputs(prefix, fp); fwrite(line, 1, len, fp); } count++; } if (error) goto error; error = 0; error: free(line); dns_p_free(packet); return error; } static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; size_t len; while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) { if (len >= sizeof line) return EOVERFLOW; /* shouldn't be possible */ fputs(prefix, fp); fwrite(line, 1, len, fp); } return 0; } static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) { const void *addr; const char *path; socklen_t len; int error; if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) { char ip[INET6_ADDRSTRLEN + 1]; if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip))) return error; fprintf(fp, "%s%s\n", prefix, ip); } else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) { fprintf(fp, "%sunix:%.*s", prefix, (int)len, path); } else { return EINVAL; } return 0; } static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) { char time_s[48], elapsed_s[48]; dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0)); dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed); fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id); fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s); fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI); return 0; } static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) { fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none"); return 0; } dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) { struct dns_trace_event *te = NULL; struct { dns_trace_id_t id; dns_microseconds_t begin, elapsed; } state = { 0 }; int error; if (!trace || !trace->fp) return EINVAL; if (0 != fseek(trace->fp, 0, SEEK_SET)) goto syerr; while (dns_trace_fget(&te, trace->fp, &error)) { size_t datasize = dns_te_datasize(te); const unsigned char *data = (datasize)? te->data : NULL; if (state.id != te->id) { state.id = te->id; state.begin = dns_ts2us(&te->ts, 0); } dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin); switch(te->type) { case DNS_TE_RES_SUBMIT: fprintf(fp, "dns_res_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " qname: %s\n", te->res_submit.qname); fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype)); fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass)); dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp); break; case DNS_TE_RES_FETCH: fprintf(fp, "dns_res_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_SUBMIT: fprintf(fp, "dns_so_submit:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); fprintf(fp, " hname: %s\n", te->so_submit.hname); dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp); dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_VERIFY: fprintf(fp, "dns_so_verify:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SO_FETCH: fprintf(fp, "dns_so_fetch:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp); if (data) { fprintf(fp, " packet: |\n"); if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) goto error; fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; case DNS_TE_SYS_CONNECT: { int socktype = te->sys_connect.socktype; fprintf(fp, "dns_sys_connect:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp); break; } case DNS_TE_SYS_SEND: { int socktype = te->sys_send.socktype; fprintf(fp, "dns_sys_send:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } case DNS_TE_SYS_RECV: { int socktype = te->sys_recv.socktype; fprintf(fp, "dns_sys_recv:\n"); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp); dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp); fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } default: fprintf(fp, "unknown(0x%.2x):\n", te->type); dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); if (data) { fprintf(fp, " data: |\n"); if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) goto error; } break; } } goto epilog; syerr: error = errno; error: (void)0; epilog: free(te); return error; } /* * H O S T S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hosts { struct dns_hosts_entry { char host[DNS_D_MAXNAME + 1]; char arpa[73 + 1]; int af; union { struct in_addr a4; struct in6_addr a6; } addr; _Bool alias; struct dns_hosts_entry *next; } *head, **tail; dns_atomic_t refcount; }; /* struct dns_hosts */ struct dns_hosts *dns_hosts_open(int *error) { static const struct dns_hosts hosts_initializer = { .refcount = 1 }; struct dns_hosts *hosts; if (!(hosts = malloc(sizeof *hosts))) goto syerr; *hosts = hosts_initializer; hosts->tail = &hosts->head; return hosts; syerr: *error = dns_syerr(); free(hosts); return 0; } /* dns_hosts_open() */ void dns_hosts_close(struct dns_hosts *hosts) { struct dns_hosts_entry *ent, *xnt; if (!hosts || 1 != dns_hosts_release(hosts)) return; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; free(ent); } free(hosts); return; } /* dns_hosts_close() */ dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) { return dns_atomic_fetch_add(&hosts->refcount); } /* dns_hosts_acquire() */ dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) { return dns_atomic_fetch_sub(&hosts->refcount); } /* dns_hosts_release() */ struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) { if (hosts) dns_hosts_release(hosts); return hosts; } /* dns_hosts_mortal() */ struct dns_hosts *dns_hosts_local(int *error_) { struct dns_hosts *hosts; int error; if (!(hosts = dns_hosts_open(&error))) goto error; if ((error = dns_hosts_loadpath(hosts, "/etc/hosts"))) goto error; return hosts; error: *error_ = error; dns_hosts_close(hosts); return 0; } /* dns_hosts_local() */ #define dns_hosts_issep(ch) (dns_isspace(ch)) #define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry ent; char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1]; unsigned wp, wc, skip; int ch, error; rewind(fp); do { memset(&ent, '\0', sizeof ent); wc = 0; skip = 0; do { memset(word, '\0', sizeof word); wp = 0; while (EOF != (ch = fgetc(fp)) && ch != '\n') { skip |= !!dns_hosts_iscom(ch); if (skip) continue; if (dns_hosts_issep(ch)) break; if (wp < sizeof word - 1) word[wp] = ch; wp++; } if (!wp) continue; wc++; switch (wc) { case 0: break; case 1: ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET; skip = (1 != dns_inet_pton(ent.af, word, &ent.addr)); break; default: if (!wp) break; dns_d_anchor(ent.host, sizeof ent.host, word, wp); if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2)))) return error; break; } /* switch() */ } while (ch != EOF && ch != '\n'); } while (ch != EOF); return 0; } /* dns_hosts_loadfile() */ int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_hosts_loadfile(hosts, fp); fclose(fp); return error; } /* dns_hosts_loadpath() */ int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) { struct dns_hosts_entry *ent, *xnt; char addr[INET6_ADDRSTRLEN + 1]; unsigned i; for (ent = hosts->head; ent; ent = xnt) { xnt = ent->next; dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr); fputs(addr, fp); for (i = strlen(addr); i < INET_ADDRSTRLEN; i++) fputc(' ', fp); fputc(' ', fp); fputs(ent->host, fp); fputc('\n', fp); } return 0; } /* dns_hosts_dump() */ int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) { struct dns_hosts_entry *ent; int error; if (!(ent = malloc(sizeof *ent))) goto syerr; dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host)); switch ((ent->af = af)) { case AF_INET6: memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6); dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr); break; case AF_INET: memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4); dns_a_arpa(ent->arpa, sizeof ent->arpa, addr); break; default: error = EINVAL; goto error; } /* switch() */ ent->alias = alias; ent->next = 0; *hosts->tail = ent; hosts->tail = &ent->next; return 0; syerr: error = dns_syerr(); error: free(ent); return error; } /* dns_hosts_insert() */ struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { struct dns_packet *P = dns_p_new(512); struct dns_packet *A = 0; struct dns_rr rr; struct dns_hosts_entry *ent; int error, af; char qname[DNS_D_MAXNAME + 1]; size_t qlen; if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error))) goto error; else if (qlen >= sizeof qname) goto toolong; if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0))) goto error; switch (rr.type) { case DNS_T_PTR: for (ent = hosts->head; ent; ent = ent->next) { if (ent->alias || 0 != strcasecmp(qname, ent->arpa)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host))) goto error; } break; case DNS_T_AAAA: af = AF_INET6; goto loop; case DNS_T_A: af = AF_INET; loop: for (ent = hosts->head; ent; ent = ent->next) { if (ent->af != af || 0 != strcasecmp(qname, ent->host)) continue; if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr))) goto error; } break; default: break; } /* switch() */ if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; dns_p_free(A); return 0; } /* dns_hosts_query() */ /* * R E S O L V . C O N F R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_resolv_conf *dns_resconf_open(int *error) { static const struct dns_resolv_conf resconf_initializer = { .lookup = "bf", .family = { AF_INET, AF_INET6 }, .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, }, .iface = { .ss_family = AF_INET }, }; struct dns_resolv_conf *resconf; struct sockaddr_in *sin; if (!(resconf = malloc(sizeof *resconf))) goto syerr; *resconf = resconf_initializer; sin = (struct sockaddr_in *)&resconf->nameserver[0]; sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(53); #if defined(SA_LEN) sin->sin_len = sizeof *sin; #endif if (0 != gethostname(resconf->search[0], sizeof resconf->search[0])) goto syerr; - dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); - dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); - /* - * XXX: If gethostname() returned a string without any label - * separator, then search[0][0] should be NUL. + * If gethostname() returned a string without any label + * separator, then search[0][0] should be NUL. */ + if (strchr (resconf->search[0], '.')) { + dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + } else { + memset (resconf->search[0], 0, sizeof resconf->search[0]); + } dns_resconf_acquire(resconf); return resconf; syerr: *error = dns_syerr(); free(resconf); return 0; } /* dns_resconf_open() */ void dns_resconf_close(struct dns_resolv_conf *resconf) { if (!resconf || 1 != dns_resconf_release(resconf)) return /* void */; free(resconf); } /* dns_resconf_close() */ dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_add(&resconf->_.refcount); } /* dns_resconf_acquire() */ dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) { return dns_atomic_fetch_sub(&resconf->_.refcount); } /* dns_resconf_release() */ struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) { if (resconf) dns_resconf_release(resconf); return resconf; } /* dns_resconf_mortal() */ struct dns_resolv_conf *dns_resconf_local(int *error_) { struct dns_resolv_conf *resconf; int error; if (!(resconf = dns_resconf_open(&error))) goto error; if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) { /* * NOTE: Both the glibc and BIND9 resolvers ignore a missing * /etc/resolv.conf, defaulting to a nameserver of * 127.0.0.1. See also dns_hints_insert_resconf, and the * default initialization of nameserver[0] in * dns_resconf_open. */ if (error != ENOENT) goto error; } if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) { if (error != ENOENT) goto error; } return resconf; error: *error_ = error; dns_resconf_close(resconf); return 0; } /* dns_resconf_local() */ struct dns_resolv_conf *dns_resconf_root(int *error) { struct dns_resolv_conf *resconf; if ((resconf = dns_resconf_local(error))) resconf->options.recurse = 1; return resconf; } /* dns_resconf_root() */ static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) { return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout); } /* dns_resconf_timeout() */ enum dns_resconf_keyword { DNS_RESCONF_NAMESERVER, DNS_RESCONF_DOMAIN, DNS_RESCONF_SEARCH, DNS_RESCONF_LOOKUP, DNS_RESCONF_FILE, DNS_RESCONF_BIND, DNS_RESCONF_CACHE, DNS_RESCONF_FAMILY, DNS_RESCONF_INET4, DNS_RESCONF_INET6, DNS_RESCONF_OPTIONS, DNS_RESCONF_EDNS0, DNS_RESCONF_NDOTS, DNS_RESCONF_TIMEOUT, DNS_RESCONF_ATTEMPTS, DNS_RESCONF_ROTATE, DNS_RESCONF_RECURSE, DNS_RESCONF_SMART, DNS_RESCONF_TCP, DNS_RESCONF_TCPx, DNS_RESCONF_INTERFACE, DNS_RESCONF_ZERO, DNS_RESCONF_ONE, DNS_RESCONF_ENABLE, DNS_RESCONF_ONLY, DNS_RESCONF_DISABLE, }; /* enum dns_resconf_keyword */ static enum dns_resconf_keyword dns_resconf_keyword(const char *word) { static const char *words[] = { [DNS_RESCONF_NAMESERVER] = "nameserver", [DNS_RESCONF_DOMAIN] = "domain", [DNS_RESCONF_SEARCH] = "search", [DNS_RESCONF_LOOKUP] = "lookup", [DNS_RESCONF_FILE] = "file", [DNS_RESCONF_BIND] = "bind", [DNS_RESCONF_CACHE] = "cache", [DNS_RESCONF_FAMILY] = "family", [DNS_RESCONF_INET4] = "inet4", [DNS_RESCONF_INET6] = "inet6", [DNS_RESCONF_OPTIONS] = "options", [DNS_RESCONF_EDNS0] = "edns0", [DNS_RESCONF_ROTATE] = "rotate", [DNS_RESCONF_RECURSE] = "recurse", [DNS_RESCONF_SMART] = "smart", [DNS_RESCONF_TCP] = "tcp", [DNS_RESCONF_INTERFACE] = "interface", [DNS_RESCONF_ZERO] = "0", [DNS_RESCONF_ONE] = "1", [DNS_RESCONF_ENABLE] = "enable", [DNS_RESCONF_ONLY] = "only", [DNS_RESCONF_DISABLE] = "disable", }; unsigned i; for (i = 0; i < lengthof(words); i++) { if (words[i] && 0 == strcasecmp(words[i], word)) return i; } if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1)) return DNS_RESCONF_NDOTS; if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1)) return DNS_RESCONF_TIMEOUT; if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1)) return DNS_RESCONF_ATTEMPTS; if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1)) return DNS_RESCONF_TCPx; return -1; } /* dns_resconf_keyword() */ /** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */ int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) { struct { char buf[128], *p; } addr = { "", addr.buf }; unsigned short port = 0; int ch, af = AF_INET, error; + memset(ss, 0, sizeof *ss); while ((ch = *src++)) { switch (ch) { case ' ': /* FALL THROUGH */ case '\t': break; case '[': break; case ']': while ((ch = *src++)) { if (dns_isdigit(ch)) { port *= 10; port += ch - '0'; } } goto inet; case ':': af = AF_INET6; /* FALL THROUGH */ default: if (addr.p < endof(addr.buf) - 1) *addr.p++ = ch; break; } /* switch() */ } /* while() */ inet: if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL)))) return error; port = (!port)? 53 : port; *dns_sa_port(af, ss) = htons(port); dns_sa_family(ss) = af; return 0; } /* dns_resconf_pton() */ #define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',') #define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';') int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { unsigned sa_count = 0; char words[6][DNS_D_MAXNAME + 1]; unsigned wp, wc, i, j, n; int ch, error; rewind(fp); do { memset(words, '\0', sizeof words); wp = 0; wc = 0; while (EOF != (ch = getc(fp)) && ch != '\n') { if (dns_resconf_issep(ch)) { if (wp > 0) { wp = 0; if (++wc >= lengthof(words)) goto skip; } } else if (dns_resconf_iscom(ch)) { skip: do { ch = getc(fp); } while (ch != EOF && ch != '\n'); break; } else if (wp < sizeof words[wc] - 1) { words[wc][wp++] = ch; } else { wp = 0; /* drop word */ goto skip; } } if (wp > 0) wc++; if (wc < 2) continue; switch (dns_resconf_keyword(words[0])) { case DNS_RESCONF_NAMESERVER: if (sa_count >= lengthof(resconf->nameserver)) continue; if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1]))) continue; sa_count++; break; case DNS_RESCONF_DOMAIN: case DNS_RESCONF_SEARCH: memset(resconf->search, '\0', sizeof resconf->search); for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++) dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i])); break; case DNS_RESCONF_LOOKUP: for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_FILE: resconf->lookup[j++] = 'f'; break; case DNS_RESCONF_BIND: resconf->lookup[j++] = 'b'; break; case DNS_RESCONF_CACHE: resconf->lookup[j++] = 'c'; break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_FAMILY: for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_INET4: resconf->family[j++] = AF_INET; break; case DNS_RESCONF_INET6: resconf->family[j++] = AF_INET6; break; default: break; } } break; case DNS_RESCONF_OPTIONS: for (i = 1; i < wc; i++) { switch (dns_resconf_keyword(words[i])) { case DNS_RESCONF_EDNS0: resconf->options.edns0 = 1; break; case DNS_RESCONF_NDOTS: for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.ndots = n; break; case DNS_RESCONF_TIMEOUT: for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.timeout = n; break; case DNS_RESCONF_ATTEMPTS: for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { n *= 10; n += words[i][j] - '0'; } /* for() */ resconf->options.attempts = n; break; case DNS_RESCONF_ROTATE: resconf->options.rotate = 1; break; case DNS_RESCONF_RECURSE: resconf->options.recurse = 1; break; case DNS_RESCONF_SMART: resconf->options.smart = 1; break; case DNS_RESCONF_TCP: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_TCPx: switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) { case DNS_RESCONF_ENABLE: resconf->options.tcp = DNS_RESCONF_TCP_ENABLE; break; case DNS_RESCONF_ONE: case DNS_RESCONF_ONLY: resconf->options.tcp = DNS_RESCONF_TCP_ONLY; break; case DNS_RESCONF_ZERO: case DNS_RESCONF_DISABLE: resconf->options.tcp = DNS_RESCONF_TCP_DISABLE; break; default: break; } /* switch() */ break; default: break; } /* switch() */ } /* for() */ break; case DNS_RESCONF_INTERFACE: for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) { n *= 10; n += words[2][i] - '0'; } dns_resconf_setiface(resconf, words[1], n); break; default: break; } /* switch() */ } while (ch != EOF); return 0; } /* dns_resconf_loadfile() */ int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_resconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_resconf_loadpath() */ struct dns_anyconf { char *token[16]; unsigned count; char buffer[1024], *tp, *cp; }; /* struct dns_anyconf */ static void dns_anyconf_reset(struct dns_anyconf *cf) { cf->count = 0; cf->tp = cf->cp = cf->buffer; } /* dns_anyconf_reset() */ static int dns_anyconf_push(struct dns_anyconf *cf) { if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token))) return ENOMEM; *cf->cp++ = '\0'; cf->token[cf->count++] = cf->tp; cf->tp = cf->cp; return 0; } /* dns_anyconf_push() */ static void dns_anyconf_pop(struct dns_anyconf *cf) { if (cf->count > 0) { --cf->count; cf->tp = cf->cp = cf->token[cf->count]; cf->token[cf->count] = 0; } } /* dns_anyconf_pop() */ static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) { if (!(cf->cp < endof(cf->buffer))) return ENOMEM; *cf->cp++ = ch; return 0; } /* dns_anyconf_addc() */ static _Bool dns_anyconf_match(const char *pat, int mc) { _Bool match; int pc; if (*pat == '^') { match = 0; ++pat; } else { match = 1; } while ((pc = *(const unsigned char *)pat++)) { switch (pc) { case '%': if (!(pc = *(const unsigned char *)pat++)) return !match; switch (pc) { case 'a': if (dns_isalpha(mc)) return match; break; case 'd': if (dns_isdigit(mc)) return match; break; case 'w': if (dns_isalnum(mc)) return match; break; case 's': if (dns_isspace(mc)) return match; break; default: if (mc == pc) return match; break; } /* switch() */ break; default: if (mc == pc) return match; break; } /* switch() */ } /* while() */ return !match; } /* dns_anyconf_match() */ static int dns_anyconf_peek(FILE *fp) { int ch; ch = getc(fp); ungetc(ch, fp); return ch; } /* dns_anyconf_peek() */ static size_t dns_anyconf_skip(const char *pat, FILE *fp) { size_t count = 0; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { count++; continue; } ungetc(ch, fp); break; } return count; } /* dns_anyconf_skip() */ static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) { size_t len; int ch; while (EOF != (ch = getc(fp))) { if (dns_anyconf_match(pat, ch)) { if ((*error = dns_anyconf_addc(cf, ch))) return 0; continue; } else { ungetc(ch, fp); break; } } if ((len = cf->cp - cf->tp)) { if ((*error = dns_anyconf_push(cf))) return 0; return len; } else { *error = 0; return 0; } } /* dns_anyconf_scan() */ DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) { unsigned i; fprintf(fp, "tokens:"); for (i = 0; i < cf->count; i++) { fprintf(fp, " %s", cf->token[i]); } fputc('\n', fp); } /* dns_anyconf_dump() */ enum dns_nssconf_keyword { DNS_NSSCONF_INVALID = 0, DNS_NSSCONF_HOSTS = 1, DNS_NSSCONF_SUCCESS, DNS_NSSCONF_NOTFOUND, DNS_NSSCONF_UNAVAIL, DNS_NSSCONF_TRYAGAIN, DNS_NSSCONF_CONTINUE, DNS_NSSCONF_RETURN, DNS_NSSCONF_FILES, DNS_NSSCONF_DNS, DNS_NSSCONF_MDNS, DNS_NSSCONF_LAST, }; /* enum dns_nssconf_keyword */ static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) { static const char *list[] = { [DNS_NSSCONF_HOSTS] = "hosts", [DNS_NSSCONF_SUCCESS] = "success", [DNS_NSSCONF_NOTFOUND] = "notfound", [DNS_NSSCONF_UNAVAIL] = "unavail", [DNS_NSSCONF_TRYAGAIN] = "tryagain", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; unsigned i; for (i = 1; i < lengthof(list); i++) { if (list[i] && 0 == strcasecmp(list[i], word)) return i; } return DNS_NSSCONF_INVALID; } /* dns_nssconf_keyword() */ static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) { static const char map[] = { ['S'] = DNS_NSSCONF_SUCCESS, ['N'] = DNS_NSSCONF_NOTFOUND, ['U'] = DNS_NSSCONF_UNAVAIL, ['T'] = DNS_NSSCONF_TRYAGAIN, ['C'] = DNS_NSSCONF_CONTINUE, ['R'] = DNS_NSSCONF_RETURN, ['f'] = DNS_NSSCONF_FILES, ['F'] = DNS_NSSCONF_FILES, ['d'] = DNS_NSSCONF_DNS, ['D'] = DNS_NSSCONF_DNS, ['b'] = DNS_NSSCONF_DNS, ['B'] = DNS_NSSCONF_DNS, ['m'] = DNS_NSSCONF_MDNS, ['M'] = DNS_NSSCONF_MDNS, }; return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID; } /* dns_nssconf_c2k() */ DNS_PRAGMA_PUSH DNS_PRAGMA_QUIET static int dns_nssconf_k2c(int k) { static const char map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = 'S', [DNS_NSSCONF_NOTFOUND] = 'N', [DNS_NSSCONF_UNAVAIL] = 'U', [DNS_NSSCONF_TRYAGAIN] = 'T', [DNS_NSSCONF_CONTINUE] = 'C', [DNS_NSSCONF_RETURN] = 'R', [DNS_NSSCONF_FILES] = 'f', [DNS_NSSCONF_DNS] = 'b', [DNS_NSSCONF_MDNS] = 'm', }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?'; } /* dns_nssconf_k2c() */ static const char *dns_nssconf_k2s(int k) { static const char *const map[DNS_NSSCONF_LAST] = { [DNS_NSSCONF_SUCCESS] = "SUCCESS", [DNS_NSSCONF_NOTFOUND] = "NOTFOUND", [DNS_NSSCONF_UNAVAIL] = "UNAVAIL", [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN", [DNS_NSSCONF_CONTINUE] = "continue", [DNS_NSSCONF_RETURN] = "return", [DNS_NSSCONF_FILES] = "files", [DNS_NSSCONF_DNS] = "dns", [DNS_NSSCONF_MDNS] = "mdns", }; return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : ""; } /* dns_nssconf_k2s() */ DNS_PRAGMA_POP int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { enum dns_nssconf_keyword source, status, action; char lookup[sizeof resconf->lookup] = "", *lp; struct dns_anyconf cf; size_t i; int error; while (!feof(fp) && !ferror(fp)) { dns_anyconf_reset(&cf); dns_anyconf_skip("%s", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) goto nextent; if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0])) goto nextent; dns_anyconf_pop(&cf); if (!dns_anyconf_skip(": \t", fp)) goto nextent; *(lp = lookup) = '\0'; while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_skip(" \t", fp); if ('[' == dns_anyconf_peek(fp)) { - dns_anyconf_skip("[ \t", fp); - - for (;;) { - if ('!' == dns_anyconf_peek(fp)) { - dns_anyconf_skip("! \t", fp); - /* FIXME: negating statuses; currently not implemented */ - dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ - break; - } + dns_anyconf_skip("[! \t", fp); - if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) break; + while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_skip("= \t", fp); if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) { dns_anyconf_pop(&cf); /* discard status */ dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ break; } dns_anyconf_skip(" \t", fp); } dns_anyconf_skip("] \t", fp); } if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */ goto nextsrc; source = dns_nssconf_keyword(cf.token[0]); switch (source) { case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: case DNS_NSSCONF_FILES: *lp++ = dns_nssconf_k2c(source); break; default: goto nextsrc; } for (i = 1; i + 1 < cf.count; i += 2) { status = dns_nssconf_keyword(cf.token[i]); action = dns_nssconf_keyword(cf.token[i + 1]); switch (status) { case DNS_NSSCONF_SUCCESS: case DNS_NSSCONF_NOTFOUND: case DNS_NSSCONF_UNAVAIL: case DNS_NSSCONF_TRYAGAIN: *lp++ = dns_nssconf_k2c(status); break; default: continue; } switch (action) { case DNS_NSSCONF_CONTINUE: case DNS_NSSCONF_RETURN: break; default: action = (status == DNS_NSSCONF_SUCCESS) ? DNS_NSSCONF_RETURN : DNS_NSSCONF_CONTINUE; break; } *lp++ = dns_nssconf_k2c(action); } nextsrc: *lp = '\0'; dns_anyconf_reset(&cf); } nextent: dns_anyconf_skip("^\n", fp); } if (*lookup) strncpy(resconf->lookup, lookup, sizeof resconf->lookup); return 0; } /* dns_nssconf_loadfile() */ int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { FILE *fp; int error; if (!(fp = dns_fopen(path, "rt", &error))) return error; error = dns_nssconf_loadfile(resconf, fp); fclose(fp); return error; } /* dns_nssconf_loadpath() */ struct dns_nssconf_source { enum dns_nssconf_keyword source, success, notfound, unavail, tryagain; }; /* struct dns_nssconf_source */ typedef unsigned dns_nssconf_i; static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) { return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0; } /* dns_nssconf_peek() */ static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) { int source, status, action; src->source = DNS_NSSCONF_INVALID; src->success = DNS_NSSCONF_RETURN; src->notfound = DNS_NSSCONF_CONTINUE; src->unavail = DNS_NSSCONF_CONTINUE; src->tryagain = DNS_NSSCONF_CONTINUE; while ((source = dns_nssconf_peek(resconf, *state))) { source = dns_nssconf_c2k(source); ++*state; switch (source) { case DNS_NSSCONF_FILES: case DNS_NSSCONF_DNS: case DNS_NSSCONF_MDNS: src->source = source; break; default: continue; } while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) { status = dns_nssconf_c2k(status); action = dns_nssconf_c2k(action); switch (action) { case DNS_NSSCONF_RETURN: case DNS_NSSCONF_CONTINUE: break; default: goto done; } switch (status) { case DNS_NSSCONF_SUCCESS: src->success = action; break; case DNS_NSSCONF_NOTFOUND: src->notfound = action; break; case DNS_NSSCONF_UNAVAIL: src->unavail = action; break; case DNS_NSSCONF_TRYAGAIN: src->tryagain = action; break; default: goto done; } *state += 2; } break; } done: return src->source != DNS_NSSCONF_INVALID; } /* dns_nssconf_next() */ static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) { switch (status) { case DNS_NSSCONF_SUCCESS: if (action == DNS_NSSCONF_RETURN) return 0; break; default: if (action == DNS_NSSCONF_CONTINUE) return 0; break; } fputc(' ', fp); if (!*count) fputc('[', fp); fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action)); ++*count; return 0; } /* dns_nssconf_dump_status() */ int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { struct dns_nssconf_source src; dns_nssconf_i i = 0; fputs("hosts:", fp); while (dns_nssconf_next(&src, resconf, &i)) { unsigned n = 0; fprintf(fp, " %s", dns_nssconf_k2s(src.source)); dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp); dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp); if (n) fputc(']', fp); } fputc('\n', fp); return 0; } /* dns_nssconf_dump() */ int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) { int af = (strchr(addr, ':'))? AF_INET6 : AF_INET; int error; + memset(&resconf->iface, 0, sizeof (struct sockaddr_storage)); if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL)))) return error; *dns_sa_port(af, &resconf->iface) = htons(port); resconf->iface.ss_family = af; return 0; } /* dns_resconf_setiface() */ #define DNS_SM_RESTORE \ do { \ pc = 0xff & (*state >> 0); \ srchi = 0xff & (*state >> 8); \ ndots = 0xff & (*state >> 16); \ } while (0) #define DNS_SM_SAVE \ do { \ *state = ((0xff & pc) << 0) \ | ((0xff & srchi) << 8) \ | ((0xff & ndots) << 16); \ } while (0) size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) { unsigned pc, srchi, ndots, len; DNS_SM_ENTER; /* if FQDN then return as-is and finish */ if (dns_d_isanchored(qname, qlen)) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); DNS_SM_EXIT; } ndots = dns_d_ndots(qname, qlen); if (ndots >= resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) { struct dns_buf buf = DNS_B_INTO(dst, lim); const char *dn = resconf->search[srchi++]; dns_b_put(&buf, qname, qlen); dns_b_putc(&buf, '.'); dns_b_puts(&buf, dn); if (!dns_d_isanchored(dn, strlen(dn))) dns_b_putc(&buf, '.'); len = dns_b_strllen(&buf); DNS_SM_YIELD(len); } if (ndots < resconf->options.ndots) { len = dns_d_anchor(dst, lim, qname, qlen); DNS_SM_YIELD(len); } DNS_SM_LEAVE; return dns_strlcpy(dst, "", lim); } /* dns_resconf_search() */ #undef DNS_SM_SAVE #undef DNS_SM_RESTORE int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { unsigned i; int af; for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; unsigned short port; dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr); port = ntohs(*dns_sa_port(af, &resconf->nameserver[i])); if (port == 53) fprintf(fp, "nameserver %s\n", addr); else fprintf(fp, "nameserver [%s]:%hu\n", addr, port); } fprintf(fp, "search"); for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++) fprintf(fp, " %s", resconf->search[i]); fputc('\n', fp); fputs("; ", fp); dns_nssconf_dump(resconf, fp); fprintf(fp, "lookup"); for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) { switch (resconf->lookup[i]) { case 'b': fprintf(fp, " bind"); break; case 'f': fprintf(fp, " file"); break; case 'c': fprintf(fp, " cache"); break; } } fputc('\n', fp); fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts); if (resconf->options.edns0) fprintf(fp, " edns0"); if (resconf->options.rotate) fprintf(fp, " rotate"); if (resconf->options.recurse) fprintf(fp, " recurse"); if (resconf->options.smart) fprintf(fp, " smart"); switch (resconf->options.tcp) { case DNS_RESCONF_TCP_ENABLE: break; case DNS_RESCONF_TCP_ONLY: fprintf(fp, " tcp"); break; case DNS_RESCONF_TCP_SOCKS: fprintf(fp, " tcp:socks"); break; case DNS_RESCONF_TCP_DISABLE: fprintf(fp, " tcp:disable"); break; } fputc('\n', fp); if ((af = resconf->iface.ss_family) != AF_UNSPEC) { char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr); fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface))); } return 0; } /* dns_resconf_dump() */ /* * H I N T S E R V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_hints_soa { unsigned char zone[DNS_D_MAXNAME + 1]; struct { struct sockaddr_storage ss; unsigned priority; } addrs[16]; unsigned count; struct dns_hints_soa *next; }; /* struct dns_hints_soa */ struct dns_hints { dns_atomic_t refcount; struct dns_hints_soa *head; }; /* struct dns_hints */ struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) { static const struct dns_hints H_initializer; struct dns_hints *H; (void)resconf; if (!(H = malloc(sizeof *H))) goto syerr; *H = H_initializer; dns_hints_acquire(H); return H; syerr: *error = dns_syerr(); free(H); return 0; } /* dns_hints_open() */ void dns_hints_close(struct dns_hints *H) { struct dns_hints_soa *soa, *nxt; if (!H || 1 != dns_hints_release(H)) return /* void */; for (soa = H->head; soa; soa = nxt) { nxt = soa->next; free(soa); } free(H); return /* void */; } /* dns_hints_close() */ dns_refcount_t dns_hints_acquire(struct dns_hints *H) { return dns_atomic_fetch_add(&H->refcount); } /* dns_hints_acquire() */ dns_refcount_t dns_hints_release(struct dns_hints *H) { return dns_atomic_fetch_sub(&H->refcount); } /* dns_hints_release() */ struct dns_hints *dns_hints_mortal(struct dns_hints *hints) { if (hints) dns_hints_release(hints); return hints; } /* dns_hints_mortal() */ struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) { struct dns_hints *hints = 0; int error; if (resconf) dns_resconf_acquire(resconf); else if (!(resconf = dns_resconf_local(&error))) goto error; if (!(hints = dns_hints_open(resconf, &error))) goto error; error = 0; if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error) goto error; dns_resconf_close(resconf); return hints; error: *error_ = error; dns_resconf_close(resconf); dns_hints_close(hints); return 0; } /* dns_hints_local() */ struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) { static const struct { int af; char addr[INET6_ADDRSTRLEN]; } root_hints[] = { { AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */ { AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */ { AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */ { AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */ { AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */ { AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */ { AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */ { AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */ { AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */ { AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */ { AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */ { AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */ { AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */ { AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */ { AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */ }; struct dns_hints *hints = 0; struct sockaddr_storage ss; unsigned i; int error, af; if (!(hints = dns_hints_open(resconf, &error))) goto error; for (i = 0; i < lengthof(root_hints); i++) { af = root_hints[i].af; + memset(&ss, 0, sizeof ss); if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL)))) goto error; *dns_sa_port(af, &ss) = htons(53); ss.ss_family = af; if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1))) goto error; } return hints; error: *error_ = error; dns_hints_close(hints); return 0; } /* dns_hints_root() */ static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) { struct dns_hints_soa *soa; for (soa = H->head; soa; soa = soa->next) { if (0 == strcasecmp(zone, (char *)soa->zone)) return soa; } return 0; } /* dns_hints_fetch() */ int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) { static const struct dns_hints_soa soa_initializer; struct dns_hints_soa *soa; unsigned i; if (!(soa = dns_hints_fetch(H, zone))) { if (!(soa = malloc(sizeof *soa))) return dns_syerr(); *soa = soa_initializer; dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone); soa->next = H->head; H->head = soa; } i = soa->count % lengthof(soa->addrs); memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa)); soa->addrs[i].priority = DNS_PP_MAX(1, priority); if (soa->count < lengthof(soa->addrs)) soa->count++; return 0; } /* dns_hints_insert() */ static _Bool dns_hints_isinaddr_any(const void *sa) { struct in_addr *addr; if (dns_sa_family(sa) != AF_INET) return 0; addr = dns_sa_addr(AF_INET, sa, NULL); return addr->s_addr == htonl(INADDR_ANY); } unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) { unsigned i, n, p; int error; for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) { union { struct sockaddr_in sin; } tmp; struct sockaddr *ns; /* * dns_resconf_open initializes nameserver[0] to INADDR_ANY. * * Traditionally the semantics of 0.0.0.0 meant the default * interface, which evolved to mean the loopback interface. * See comment block preceding resolv/res_init.c:res_init in * glibc 2.23. As of 2.23, glibc no longer translates * 0.0.0.0 despite the code comment, but it does default to * 127.0.0.1 when no nameservers are present. * * BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1. * See lib/lwres/lwconfig.c:lwres_create_addr and the * convert_zero flag. 127.0.0.1 is also the default when no * nameservers are present. */ if (dns_hints_isinaddr_any(&resconf->nameserver[i])) { memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin); tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); ns = (struct sockaddr *)&tmp.sin; } else { ns = (struct sockaddr *)&resconf->nameserver[i]; } if ((error = dns_hints_insert(H, zone, ns, p))) goto error; p += !resconf->options.rotate; } return n; error: *error_ = error; return n; } /* dns_hints_insert_resconf() */ static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) { int cmp; if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority)) return cmp; return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed); } /* dns_hints_i_cmp() */ static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned p0, p; p0 = 0; for (p = 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) < 0) p0 = p; } return p0; } /* dns_hints_i_start() */ static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) { unsigned pZ, p; for (pZ = 0; pZ < soa->count; pZ++) { if (dns_hints_i_cmp(pZ, p0, i, soa) > 0) goto cont; } return soa->count; cont: for (p = pZ + 1; p < soa->count; p++) { if (dns_hints_i_cmp(p, p0, i, soa) <= 0) continue; if (dns_hints_i_cmp(p, pZ, i, soa) >= 0) continue; pZ = p; } return pZ; } /* dns_hints_i_skip() */ static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) { static const struct dns_hints_i i_initializer; struct dns_hints_soa *soa; i->state = i_initializer.state; do { i->state.seed = dns_random(); } while (0 == i->state.seed); if ((soa = dns_hints_fetch(hints, i->zone))) { i->state.next = dns_hints_i_start(i, soa); } return i; } /* dns_hints_i_init() */ unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) { struct dns_hints_soa *soa; unsigned n; if (!(soa = dns_hints_fetch(H, i->zone))) return 0; n = 0; while (i->state.next < soa->count && n < lim) { *sa = (struct sockaddr *)&soa->addrs[i->state.next].ss; *sa_len = dns_sa_len(*sa); sa++; sa_len++; n++; i->state.next = dns_hints_i_skip(i->state.next, i, soa); } return n; } /* dns_hints_grep() */ struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { struct dns_packet *A, *P; struct dns_rr rr; char zone[DNS_D_MAXNAME + 1]; size_t zlen; struct dns_hints_i i; struct sockaddr *sa; socklen_t slen; int error; if (!dns_rr_grep(&rr, 1, dns_rr_i_new(Q, .section = DNS_S_QUESTION), Q, &error)) goto error; if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) goto error; else if (zlen >= sizeof zone) goto toolong; P = dns_p_new(512); dns_header(P)->qr = 1; if ((error = dns_rr_copy(P, &rr, Q))) goto error; if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local."))) goto error; do { i.zone = zone; dns_hints_i_init(&i, hints); while (dns_hints_grep(&sa, &slen, 1, &i, hints)) { int af = sa->sa_family; int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A; if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL)))) goto error; } } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen))); if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) goto error; return A; toolong: error = DNS_EILLEGAL; error: *error_ = error; return 0; } /* dns_hints_query() */ /** ugly hack to support specifying ports other than 53 in resolv.conf. */ static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) { struct dns_hints_soa *soa; void *addrsoa; socklen_t addrlen; unsigned short port; unsigned i; for (soa = hints->head; soa; soa = soa->next) { for (i = 0; i < soa->count; i++) { if (af != soa->addrs[i].ss.ss_family) continue; if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen))) continue; if (memcmp(addr, addrsoa, addrlen)) continue; port = *dns_sa_port(af, &soa->addrs[i].ss); return (port)? port : htons(53); } } return htons(53); } /* dns_hints_port() */ int dns_hints_dump(struct dns_hints *hints, FILE *fp) { struct dns_hints_soa *soa; char addr[INET6_ADDRSTRLEN]; unsigned i; int af, error; for (soa = hints->head; soa; soa = soa->next) { fprintf(fp, "ZONE \"%s\"\n", soa->zone); for (i = 0; i < soa->count; i++) { af = soa->addrs[i].ss.ss_family; if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr))) return error; fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss))); } } return 0; } /* dns_hints_dump() */ /* * C A C H E R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) { return dns_atomic_fetch_add(&cache->_.refcount); } /* dns_cache_acquire() */ static dns_refcount_t dns_cache_release(struct dns_cache *cache) { return dns_atomic_fetch_sub(&cache->_.refcount); } /* dns_cache_release() */ static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) { (void)query; (void)cache; (void)error; return NULL; } /* dns_cache_query() */ static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) { (void)query; (void)cache; return 0; } /* dns_cache_submit() */ static int dns_cache_check(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_check() */ static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) { (void)cache; (void)error; return NULL; } /* dns_cache_fetch() */ static int dns_cache_pollfd(struct dns_cache *cache) { (void)cache; return -1; } /* dns_cache_pollfd() */ static short dns_cache_events(struct dns_cache *cache) { (void)cache; return 0; } /* dns_cache_events() */ static void dns_cache_clear(struct dns_cache *cache) { (void)cache; return; } /* dns_cache_clear() */ struct dns_cache *dns_cache_init(struct dns_cache *cache) { static const struct dns_cache c_init = { .acquire = &dns_cache_acquire, .release = &dns_cache_release, .query = &dns_cache_query, .submit = &dns_cache_submit, .check = &dns_cache_check, .fetch = &dns_cache_fetch, .pollfd = &dns_cache_pollfd, .events = &dns_cache_events, .clear = &dns_cache_clear, ._ = { .refcount = 1, }, }; *cache = c_init; return cache; } /* dns_cache_init() */ void dns_cache_close(struct dns_cache *cache) { if (cache) cache->release(cache); } /* dns_cache_close() */ /* * S O C K E T R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static void dns_socketclose(int *fd, const struct dns_options *opts) { if (opts && opts->closefd.cb) opts->closefd.cb(fd, opts->closefd.arg); if (*fd != -1) { #if _WIN32 closesocket(*fd); #else close(*fd); #endif *fd = -1; } } /* dns_socketclose() */ #ifndef HAVE_IOCTLSOCKET #define HAVE_IOCTLSOCKET (_WIN32 || _WIN64) #endif #ifndef HAVE_SOCK_CLOEXEC #ifdef SOCK_CLOEXEC #define HAVE_SOCK_CLOEXEC 1 #else #define HAVE_SOCK_CLOEXEC 0 #endif #endif #ifndef HAVE_SOCK_NONBLOCK #ifdef SOCK_NONBLOCK #define HAVE_SOCK_NONBLOCK 1 #else #define HAVE_SOCK_NONBLOCK 0 #endif #endif #define DNS_SO_MAXTRY 7 static int dns_socket(struct sockaddr *local, int type, int *error_) { int fd = -1, flags, error; #if defined FIONBIO unsigned long opt; #endif flags = 0; #if HAVE_SOCK_CLOEXEC flags |= SOCK_CLOEXEC; #endif #if HAVE_SOCK_NONBLOCK flags |= SOCK_NONBLOCK; #endif if (-1 == (fd = socket(local->sa_family, type|flags, 0))) goto soerr; #if defined F_SETFD && !HAVE_SOCK_CLOEXEC if (-1 == fcntl(fd, F_SETFD, 1)) goto syerr; #endif #if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK if (-1 == (flags = fcntl(fd, F_GETFL))) goto syerr; if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) goto syerr; #elif defined FIONBIO && HAVE_IOCTLSOCKET opt = 1; if (0 != ioctlsocket(fd, FIONBIO, &opt)) goto soerr; #endif #if defined SO_NOSIGPIPE if (type != SOCK_DGRAM) { if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, sizeof (int))) goto soerr; } #endif if (local->sa_family != AF_INET && local->sa_family != AF_INET6) return fd; if (type != SOCK_DGRAM) return fd; +#define LEAVE_SELECTION_OF_PORT_TO_KERNEL +#if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL) /* * FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by * default. Though the ephemeral range is quite small on OS X * (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu * Xenial). See also RFC 6056. * * TODO: Optionally rely on the kernel to select a random port. */ if (*dns_sa_port(local->sa_family, local) == 0) { struct sockaddr_storage tmp; unsigned i, port; memcpy(&tmp, local, dns_sa_len(local)); for (i = 0; i < DNS_SO_MAXTRY; i++) { port = 1025 + (dns_random() % 64510); *dns_sa_port(tmp.ss_family, &tmp) = htons(port); if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp))) return fd; } /* NB: continue to next bind statement */ } +#endif if (0 == bind(fd, local, dns_sa_len(local))) return fd; /* FALL THROUGH */ soerr: error = dns_soerr(); goto error; #if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK) syerr: error = dns_syerr(); goto error; #endif error: *error_ = error; dns_socketclose(&fd, NULL); return -1; } /* dns_socket() */ enum { DNS_SO_UDP_INIT = 1, DNS_SO_UDP_CONN, DNS_SO_UDP_SEND, DNS_SO_UDP_RECV, DNS_SO_UDP_DONE, DNS_SO_TCP_INIT, DNS_SO_TCP_CONN, DNS_SO_TCP_SEND, DNS_SO_TCP_RECV, DNS_SO_TCP_DONE, DNS_SO_SOCKS_INIT, DNS_SO_SOCKS_CONN, DNS_SO_SOCKS_HELLO_SEND, DNS_SO_SOCKS_HELLO_RECV, DNS_SO_SOCKS_AUTH_SEND, DNS_SO_SOCKS_AUTH_RECV, DNS_SO_SOCKS_REQUEST_PREPARE, DNS_SO_SOCKS_REQUEST_SEND, DNS_SO_SOCKS_REQUEST_RECV, DNS_SO_SOCKS_REQUEST_RECV_V6, DNS_SO_SOCKS_HANDSHAKE_DONE, }; struct dns_socket { struct dns_options opts; int udp; int tcp; int *old; unsigned onum, olim; int type; struct sockaddr_storage local, remote; struct dns_k_permutor qids; struct dns_stat stat; struct dns_trace *trace; /* * NOTE: dns_so_reset() zeroes everything from here down. */ int state; unsigned short qid; char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_packet *query; size_t qout; /* During a SOCKS handshake the query is temporarily stored * here. */ struct dns_packet *query_backup; struct dns_clock elapsed; struct dns_packet *answer; size_t alen, apos; }; /* struct dns_socket */ /* * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have * a chance to recognize a state change after installing a persistent event * and where sequential descriptors with the same integer value returned * from _pollfd() would be ambiguous. See dns_so_closefds(). */ static int dns_so_closefd(struct dns_socket *so, int *fd) { int error; if (*fd == -1) return 0; if (so->opts.closefd.cb) { if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) { return error; } else if (*fd == -1) return 0; } if (!(so->onum < so->olim)) { unsigned olim = DNS_PP_MAX(4, so->olim * 2); void *old; if (!(old = realloc(so->old, sizeof so->old[0] * olim))) return dns_syerr(); so->old = old; so->olim = olim; } so->old[so->onum++] = *fd; *fd = -1; return 0; } /* dns_so_closefd() */ #define DNS_SO_CLOSE_UDP 0x01 #define DNS_SO_CLOSE_TCP 0x02 #define DNS_SO_CLOSE_OLD 0x04 #define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD) static void dns_so_closefds(struct dns_socket *so, int which) { if (DNS_SO_CLOSE_UDP & which) dns_socketclose(&so->udp, &so->opts); if (DNS_SO_CLOSE_TCP & which) dns_socketclose(&so->tcp, &so->opts); if (DNS_SO_CLOSE_OLD & which) { unsigned i; for (i = 0; i < so->onum; i++) dns_socketclose(&so->old[i], &so->opts); so->onum = 0; free(so->old); so->old = 0; so->olim = 0; } } /* dns_so_closefds() */ static void dns_so_destroy(struct dns_socket *); static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, }; *so = so_initializer; so->type = type; if (opts) so->opts = *opts; if (local) memcpy(&so->local, local, dns_sa_len(local)); if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error))) goto error; dns_k_permutor_init(&so->qids, 1, 65535); return so; error: dns_so_destroy(so); return 0; } /* dns_so_init() */ struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { struct dns_socket *so; if (!(so = malloc(sizeof *so))) goto syerr; if (!dns_so_init(so, local, type, opts, error)) goto error; return so; syerr: *error = dns_syerr(); error: dns_so_close(so); return 0; } /* dns_so_open() */ static void dns_so_destroy(struct dns_socket *so) { dns_so_reset(so); dns_so_closefds(so, DNS_SO_CLOSE_ALL); dns_trace_close(so->trace); } /* dns_so_destroy() */ void dns_so_close(struct dns_socket *so) { if (!so) return; dns_so_destroy(so); free(so); } /* dns_so_close() */ void dns_so_reset(struct dns_socket *so) { dns_p_setptr(&so->answer, NULL); memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state)); } /* dns_so_reset() */ unsigned short dns_so_mkqid(struct dns_socket *so) { return dns_k_permutor_step(&so->qids); } /* dns_so_mkqid() */ #define DNS_SO_MINBUF 768 static int dns_so_newanswer(struct dns_socket *so, size_t len) { size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF); void *p; if (!(p = realloc(so->answer, size))) return dns_syerr(); so->answer = dns_p_init(p, size); return 0; } /* dns_so_newanswer() */ int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) { struct dns_rr rr; int error = DNS_EUNKNOWN; dns_so_reset(so); if ((error = dns_rr_parse(&rr, 12, Q))) goto error; if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error))) goto error; /* * NOTE: Don't bail if expansion is too long; caller may be * intentionally sending long names. However, we won't be able to * verify it on return. */ so->qtype = rr.type; so->qclass = rr.class; if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF))) goto syerr; memcpy(&so->remote, host, dns_sa_len(host)); so->query = Q; so->qout = 0; dns_begin(&so->elapsed); if (dns_header(so->query)->qid == 0) dns_header(so->query)->qid = dns_so_mkqid(so); so->qid = dns_header(so->query)->qid; so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT : (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT; so->stat.queries++; dns_trace_so_submit(so->trace, Q, host, 0); return 0; syerr: error = dns_syerr(); error: dns_so_reset(so); dns_trace_so_submit(so->trace, Q, host, error); return error; } /* dns_so_submit() */ static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) { char qname[DNS_D_MAXNAME + 1]; size_t qlen; struct dns_rr rr; int error = -1; if (P->end < 12) goto reject; if (so->qid != dns_header(P)->qid) goto reject; if (!dns_p_count(P, DNS_S_QD)) goto reject; if (0 != dns_rr_parse(&rr, 12, P)) goto reject; if (rr.type != so->qtype || rr.class != so->qclass) goto reject; if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error))) goto error; else if (qlen >= sizeof qname || qlen != so->qlen) goto reject; if (0 != strcasecmp(so->qname, qname)) goto reject; dns_trace_so_verify(so->trace, P, 0); return 0; reject: error = DNS_EVERIFY; error: DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error)); dns_trace_so_verify(so->trace, P, error); return error; } /* dns_so_verify() */ static _Bool dns_so_tcp_keep(struct dns_socket *so) { struct sockaddr_storage remote; if (so->tcp == -1) return 0; if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &(socklen_t){ sizeof remote })) return 0; return 0 == dns_sa_cmp(&remote, &so->remote); } /* dns_so_tcp_keep() */ /* Convenience functions for sending non-DNS data. */ /* Set up everything for sending LENGTH octets. Returns the buffer for the data. */ static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->qout = 2; so->query->end = length; return so->query->data; } /* Set up everything for receiving LENGTH octets. */ static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) { /* Skip the length octets, we are not doing DNS. */ so->apos = 2; so->alen = length; } /* Returns the buffer containing the received data. */ static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { return so->answer->data; } #if defined __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warray-bounds" #endif static int dns_so_tcp_send(struct dns_socket *so) { unsigned char *qsrc; size_t qend; int error; size_t n; so->query->data[-2] = 0xff & (so->query->end >> 8); so->query->data[-1] = 0xff & (so->query->end >> 0); qend = so->query->end + 2; while (so->qout < qend) { qsrc = &so->query->data[-2] + so->qout; n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error); dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error); if (error) return error; so->qout += n; so->stat.tcp.sent.bytes += n; } so->stat.tcp.sent.count++; return 0; } /* dns_so_tcp_send() */ static int dns_so_tcp_recv(struct dns_socket *so) { unsigned char *asrc; size_t aend, alen, n; int error; aend = so->alen + 2; while (so->apos < aend) { asrc = &so->answer->data[-2]; n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error); dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error); if (error) return error; so->apos += n; so->stat.tcp.rcvd.bytes += n; if (so->alen == 0 && so->apos >= 2) { alen = ((0xff & so->answer->data[-2]) << 8) | ((0xff & so->answer->data[-1]) << 0); if ((error = dns_so_newanswer(so, alen))) return error; so->alen = alen; aend = alen + 2; } } so->answer->end = so->alen; so->stat.tcp.rcvd.count++; return 0; } /* dns_so_tcp_recv() */ #if __clang__ #pragma clang diagnostic pop #endif int dns_so_check(struct dns_socket *so) { int error; size_t n; unsigned char *buffer; retry: switch (so->state) { case DNS_SO_UDP_INIT: if (so->remote.ss_family != so->local.ss_family) { /* Family mismatch. Reinitialize. */ if ((error = dns_so_closefd(so, &so->udp))) goto error; if ((error = dns_so_closefd(so, &so->tcp))) goto error; /* If the user supplied an interface statement, that is gone now. Sorry. */ memset(&so->local, 0, sizeof so->local); so->local.ss_family = so->remote.ss_family; if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error))) goto error; } so->state++; /* FALL THROUGH */ case DNS_SO_UDP_CONN: + udp_connect_retry: error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error); + + /* Linux returns EINVAL when address was bound to + localhost and it's external IP address now. */ + if (error == EINVAL) { + struct sockaddr unspec_addr; + memset (&unspec_addr, 0, sizeof unspec_addr); + unspec_addr.sa_family = AF_UNSPEC; + connect(so->udp, &unspec_addr, sizeof unspec_addr); + goto udp_connect_retry; + } else if (error == ECONNREFUSED) + /* Error for previous socket operation may + be reserverd asynchronously. */ + goto udp_connect_retry; + if (error) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_SEND: n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error); dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error); if (error) goto error; so->stat.udp.sent.bytes += n; so->stat.udp.sent.count++; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_RECV: n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error); dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error); if (error) goto error; so->answer->end = n; so->stat.udp.rcvd.bytes += n; so->stat.udp.rcvd.count++; if ((error = dns_so_verify(so, so->answer))) goto trash; so->state++; /* FALL THROUGH */ case DNS_SO_UDP_DONE: if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM) return 0; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_INIT: if (so->remote.ss_family != so->local.ss_family) { /* Family mismatch. Reinitialize. */ if ((error = dns_so_closefd(so, &so->udp))) goto error; if ((error = dns_so_closefd(so, &so->tcp))) goto error; /* If the user supplied an interface statement, that is gone now. Sorry. */ memset(&so->local, 0, sizeof so->local); so->local.ss_family = so->remote.ss_family; } if (dns_so_tcp_keep(so)) { so->state = DNS_SO_TCP_SEND; goto retry; } if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_CONN: error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error); if (error && error != DNS_EISCONN) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_SEND: if ((error = dns_so_tcp_send(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_RECV: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_TCP_DONE: /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */ if (so->type != SOCK_STREAM) { if ((error = dns_so_closefd(so, &so->tcp))) goto error; } if ((error = dns_so_verify(so, so->answer))) goto error; return 0; case DNS_SO_SOCKS_INIT: if ((error = dns_so_closefd(so, &so->tcp))) goto error; if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_CONN: { unsigned char method; error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host)); dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error); if (error && error != DNS_EISCONN) goto error; /* We need to do a handshake with the SOCKS server, * but the query is already in the buffer. Move it * out of the way. */ dns_p_movptr(&so->query_backup, &so->query); /* Create a new buffer for the handshake. */ dns_p_grow(&so->query); /* Negotiate method. */ buffer = dns_so_tcp_send_buffer(so, 3); buffer[0] = 5; /* RFC-1928 VER field. */ buffer[1] = 1; /* NMETHODS */ if (so->opts.socks_user) method = 2; /* Method: username/password authentication. */ else method = 0; /* Method: No authentication required. */ buffer[2] = method; so->state++; } /* FALL THROUGH */ case DNS_SO_SOCKS_HELLO_SEND: if ((error = dns_so_tcp_send(so))) goto error; dns_so_tcp_recv_expect(so, 2); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_HELLO_RECV: { unsigned char method; if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); method = so->opts.socks_user ? 2 : 0; if (buffer[0] != 5 || buffer[1] != method) { /* Socks server returned wrong version or does not support our requested method. */ error = ENOTSUP; /* Fixme: Is there a better errno? */ goto error; } if (method == 0) { /* No authentication, go ahead and send the request. */ so->state = DNS_SO_SOCKS_REQUEST_PREPARE; goto retry; } /* Prepare username/password sub-negotiation. */ if (! so->opts.socks_password) { error = EINVAL; /* No password given. */ goto error; } else { size_t buflen, ulen, plen; ulen = strlen(so->opts.socks_user); plen = strlen(so->opts.socks_password); if (!ulen || ulen > 255 || !plen || plen > 255) { error = EINVAL; /* Credentials too long or too short. */ goto error; } buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen); buffer[0] = 1; /* VER of the sub-negotiation. */ buffer[1] = (unsigned char) ulen; buflen = 2; memcpy (buffer+buflen, so->opts.socks_user, ulen); buflen += ulen; buffer[buflen++] = (unsigned char) plen; memcpy (buffer+buflen, so->opts.socks_password, plen); } so->state++; } /* FALL THROUGH */ case DNS_SO_SOCKS_AUTH_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Skip the two length octets, and receive two octets. */ dns_so_tcp_recv_expect(so, 2); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_AUTH_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 1) { /* SOCKS server returned wrong version. */ error = EPROTO; goto error; } if (buffer[1]) { /* SOCKS server denied access. */ error = EACCES; goto error; } so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_PREPARE: /* Send request details (rfc-1928, 4). */ buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10); buffer[0] = 5; /* VER */ buffer[1] = 1; /* CMD = CONNECT */ buffer[2] = 0; /* RSV */ if (so->remote.ss_family == AF_INET6) { struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote; buffer[3] = 4; /* ATYP = IPv6 */ memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */ memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */ } else { struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote; buffer[3] = 1; /* ATYP = IPv4 */ memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */ memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */ } so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_SEND: if ((error = dns_so_tcp_send(so))) goto error; /* Expect ten octets. This is the length of the * response assuming a IPv4 address is used. */ dns_so_tcp_recv_expect(so, 10); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_RECV: if ((error = dns_so_tcp_recv(so))) goto error; buffer = dns_so_tcp_recv_buffer(so); if (buffer[0] != 5 || buffer[2] != 0) { /* Socks server returned wrong version or the reserved field is not zero. */ error = EPROTO; goto error; } if (buffer[1]) { switch (buffer[1]) { case 0x01: /* general SOCKS server failure. */ error = ENETDOWN; break; case 0x02: /* connection not allowed by ruleset. */ error = EACCES; break; case 0x03: /* Network unreachable */ error = ENETUNREACH; break; case 0x04: /* Host unreachable */ error = EHOSTUNREACH; break; case 0x05: /* Connection refused */ error = ECONNREFUSED; break; case 0x06: /* TTL expired */ error = ETIMEDOUT; break; case 0x08: /* Address type not supported */ error = EPROTONOSUPPORT; break; case 0x07: /* Command not supported */ default: error = ENOTSUP; /* Fixme: Is there a better error? */ break; } goto error; } if (buffer[3] == 1) { /* This was indeed an IPv4 address. */ so->state = DNS_SO_SOCKS_HANDSHAKE_DONE; goto retry; } if (buffer[3] != 4) { error = ENOTSUP; goto error; } /* Expect receive twelve octets. This accounts for * the remaining bytes assuming an IPv6 address is * used. */ dns_so_tcp_recv_expect(so, 12); so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_REQUEST_RECV_V6: if ((error = dns_so_tcp_recv(so))) goto error; so->state++; /* FALL THROUGH */ case DNS_SO_SOCKS_HANDSHAKE_DONE: /* We have not way to store the actual address used by * the server. Then again, we don't really care. */ /* Restore the query. */ dns_p_movptr(&so->query, &so->query_backup); /* Reset cursors. */ so->qout = 0; so->apos = 0; so->alen = 0; /* SOCKS handshake is done. Proceed with the * lookup. */ so->state = DNS_SO_TCP_SEND; goto retry; default: error = DNS_EUNKNOWN; goto error; } /* switch() */ trash: DNS_CARP("discarding packet"); goto retry; error: switch (error) { case DNS_EINTR: goto retry; case DNS_EINPROGRESS: /* FALL THROUGH */ case DNS_EALREADY: /* FALL THROUGH */ #if DNS_EWOULDBLOCK != DNS_EAGAIN case DNS_EWOULDBLOCK: /* FALL THROUGH */ #endif error = DNS_EAGAIN; break; } /* switch() */ return error; } /* dns_so_check() */ struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) { struct dns_packet *answer; switch (so->state) { case DNS_SO_UDP_DONE: case DNS_SO_TCP_DONE: answer = so->answer; so->answer = 0; dns_trace_so_fetch(so->trace, answer, 0); return answer; default: *error = DNS_EUNKNOWN; dns_trace_so_fetch(so->trace, NULL, *error); return 0; } } /* dns_so_fetch() */ struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) { struct dns_packet *A; int error; if (!so->state) { if ((error = dns_so_submit(so, Q, host))) goto error; } if ((error = dns_so_check(so))) goto error; if (!(A = dns_so_fetch(so, &error))) goto error; dns_so_reset(so); return A; error: *error_ = error; return 0; } /* dns_so_query() */ time_t dns_so_elapsed(struct dns_socket *so) { return dns_elapsed(&so->elapsed); } /* dns_so_elapsed() */ void dns_so_clear(struct dns_socket *so) { dns_so_closefds(so, DNS_SO_CLOSE_OLD); } /* dns_so_clear() */ static int dns_so_events2(struct dns_socket *so, enum dns_events type) { int events = 0; switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_UDP_RECV: events |= DNS_POLLIN; break; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: events |= DNS_POLLOUT; break; case DNS_SO_TCP_RECV: events |= DNS_POLLIN; break; } /* switch() */ switch (type) { case DNS_LIBEVENT: return DNS_POLL2EV(events); default: return events; } /* switch() */ } /* dns_so_events2() */ int dns_so_events(struct dns_socket *so) { return dns_so_events2(so, so->opts.events); } /* dns_so_events() */ int dns_so_pollfd(struct dns_socket *so) { switch (so->state) { case DNS_SO_UDP_CONN: case DNS_SO_UDP_SEND: case DNS_SO_UDP_RECV: return so->udp; case DNS_SO_TCP_CONN: case DNS_SO_TCP_SEND: case DNS_SO_TCP_RECV: return so->tcp; } /* switch() */ return -1; } /* dns_so_pollfd() */ int dns_so_poll(struct dns_socket *so, int timeout) { return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout); } /* dns_so_poll() */ const struct dns_stat *dns_so_stat(struct dns_socket *so) { return &so->stat; } /* dns_so_stat() */ struct dns_trace *dns_so_trace(struct dns_socket *so) { return so->trace; } /* dns_so_trace() */ void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) { struct dns_trace *otrace = so->trace; so->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); } /* dns_so_settrace() */ /* * R E S O L V E R R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ enum dns_res_state { DNS_R_INIT, DNS_R_GLUE, DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */ DNS_R_FILE, /* Lookup in local hosts database */ DNS_R_CACHE, /* Lookup in application cache */ DNS_R_SUBMIT, DNS_R_CHECK, DNS_R_FETCH, DNS_R_BIND, /* Lookup in the network */ DNS_R_SEARCH, DNS_R_HINTS, DNS_R_ITERATE, DNS_R_FOREACH_NS, DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */ DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */ DNS_R_FOREACH_A, DNS_R_QUERY_A, DNS_R_FOREACH_AAAA, DNS_R_QUERY_AAAA, DNS_R_CNAME0_A, DNS_R_CNAME1_A, DNS_R_FINISH, DNS_R_SMART0_A, DNS_R_SMART1_A, DNS_R_DONE, DNS_R_SERVFAIL, }; /* enum dns_res_state */ #define DNS_R_MAXDEPTH 8 #define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1) struct dns_resolver { struct dns_socket so; struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_hints *hints; struct dns_cache *cache; struct dns_trace *trace; dns_atomic_t refcount; /* Reset zeroes everything below here. */ char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; enum dns_class qclass; struct dns_clock elapsed; dns_resconf_i_t search; struct dns_rr_i smart; struct dns_packet *nodata; /* answer if nothing better */ unsigned sp; struct dns_res_frame { enum dns_res_state state; int error; int which; /* (B)IND, (F)ILE; index into resconf->lookup */ int qflags; unsigned attempts; struct dns_packet *query, *answer, *hints; struct dns_rr_i hints_i, hints_j; struct dns_rr hints_ns, ans_cname; } stack[DNS_R_MAXDEPTH]; }; /* struct dns_resolver */ static int dns_res_tcp2type(int tcp) { switch (tcp) { case DNS_RESCONF_TCP_ONLY: case DNS_RESCONF_TCP_SOCKS: return SOCK_STREAM; case DNS_RESCONF_TCP_DISABLE: return SOCK_DGRAM; default: return 0; } } /* dns_res_tcp2type() */ struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) { static const struct dns_resolver R_initializer = { .refcount = 1, }; struct dns_resolver *R = 0; int type, error; /* * Grab ref count early because the caller may have passed us a mortal * reference, and we want to do the right thing if we return early * from an error. */ if (resconf) dns_resconf_acquire(resconf); if (hosts) dns_hosts_acquire(hosts); if (hints) dns_hints_acquire(hints); if (cache) dns_cache_acquire(cache); /* * Don't try to load it ourselves because a NULL object might be an * error from, say, dns_resconf_root(), and loading * dns_resconf_local() by default would create undesirable surpises. */ if (!resconf || !hosts || !hints) { if (!*_error) *_error = EINVAL; goto _error; } if (!(R = malloc(sizeof *R))) goto syerr; *R = R_initializer; type = dns_res_tcp2type(resconf->options.tcp); if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error)) goto error; R->resconf = resconf; R->hosts = hosts; R->hints = hints; R->cache = cache; return R; syerr: error = dns_syerr(); error: *_error = error; _error: dns_res_close(R); dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); dns_cache_close(cache); return 0; } /* dns_res_open() */ struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) { struct dns_resolv_conf *resconf = 0; struct dns_hosts *hosts = 0; struct dns_hints *hints = 0; struct dns_resolver *res = 0; if (!(resconf = dns_resconf_local(error))) goto epilog; if (!(hosts = dns_hosts_local(error))) goto epilog; if (!(hints = dns_hints_local(resconf, error))) goto epilog; if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error))) goto epilog; epilog: dns_resconf_close(resconf); dns_hosts_close(hosts); dns_hints_close(hints); return res; } /* dns_res_stub() */ static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) { (void)R; dns_p_setptr(&frame->query, NULL); dns_p_setptr(&frame->answer, NULL); dns_p_setptr(&frame->hints, NULL); } /* dns_res_frame_destroy() */ static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) { memset(frame, '\0', sizeof *frame); /* * NB: Can be invoked from dns_res_open, before R->resconf has been * initialized. */ if (R->resconf) { if (!R->resconf->options.recurse) frame->qflags |= DNS_Q_RD; if (R->resconf->options.edns0) frame->qflags |= DNS_Q_EDNS0; } } /* dns_res_frame_init() */ static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) { dns_res_frame_destroy(R, frame); dns_res_frame_init(R, frame); } /* dns_res_frame_reset() */ static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) { struct dns_packet *P = NULL; if (!(F < endof(R->stack))) return DNS_EUNKNOWN; dns_p_movptr(&P, &F->query); dns_res_frame_reset(R, F); dns_p_movptr(&F->query, &P); return dns_q_make(&F->query, qname, qtype, qclass, F->qflags); } /* dns_res_frame_prepare() */ void dns_res_reset(struct dns_resolver *R) { unsigned i; dns_so_reset(&R->so); dns_p_setptr(&R->nodata, NULL); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_destroy(R, &R->stack[i]); memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname)); for (i = 0; i < lengthof(R->stack); i++) dns_res_frame_init(R, &R->stack[i]); } /* dns_res_reset() */ void dns_res_close(struct dns_resolver *R) { if (!R || 1 < dns_res_release(R)) return; dns_res_reset(R); dns_so_destroy(&R->so); dns_hints_close(R->hints); dns_hosts_close(R->hosts); dns_resconf_close(R->resconf); dns_cache_close(R->cache); dns_trace_close(R->trace); free(R); } /* dns_res_close() */ dns_refcount_t dns_res_acquire(struct dns_resolver *R) { return dns_atomic_fetch_add(&R->refcount); } /* dns_res_acquire() */ dns_refcount_t dns_res_release(struct dns_resolver *R) { return dns_atomic_fetch_sub(&R->refcount); } /* dns_res_release() */ struct dns_resolver *dns_res_mortal(struct dns_resolver *res) { if (res) dns_res_release(res); return res; } /* dns_res_mortal() */ static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) { size_t bufsiz = P0->end + P1->end; struct dns_packet *P[3] = { P0, P1, 0 }; struct dns_rr rr[3]; int error, copy, i; enum dns_section section; retry: if (!(P[2] = dns_p_make(bufsiz, &error))) goto error; dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) { if ((error = dns_rr_copy(P[2], &rr[0], P[0]))) goto error; } for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) { for (i = 0; i < 2; i++) { dns_rr_foreach(&rr[i], P[i], .section = section) { copy = 1; dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) { if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) { copy = 0; break; } } if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) { if (error == DNS_ENOBUFS && bufsiz < 65535) { dns_p_setptr(&P[2], NULL); bufsiz = DNS_PP_MAX(65535, bufsiz * 2); goto retry; } goto error; } } /* foreach(rr) */ } /* foreach(packet) */ } /* foreach(section) */ return P[2]; error: *error_ = error; dns_p_free(P[2]); return 0; } /* dns_res_merge() */ static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { struct dns_packet *P = dns_p_new(512); char qname[DNS_D_MAXNAME + 1]; size_t qlen; enum dns_type qtype; struct dns_rr rr; unsigned sp; int error; if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error)) || qlen >= sizeof qname) return 0; if (!(qtype = dns_rr_type(12, Q))) return 0; if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0))) return 0; for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (dns_p_count(P, DNS_S_AN) > 0) goto copy; /* Otherwise, look for a CNAME */ for (sp = 0; sp <= R->sp; sp++) { if (!R->stack[sp].answer) continue; dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AN; if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) return 0; } } if (!dns_p_count(P, DNS_S_AN)) return 0; copy: return dns_p_copy(dns_p_make(P->end, &error), P); } /* dns_res_glue() */ /* * Sort NS records by three criteria: * * 1) Whether glue is present. * 2) Whether glue record is original or of recursive lookup. * 3) Randomly shuffle records which share the above criteria. * * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will * be added during an iteration. * * FIXME: Only groks A glue, not AAAA glue. */ static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { _Bool glued[2] = { 0 }; struct dns_rr x = { 0 }, y = { 0 }; struct dns_ns ns; int cmp, error; if (!(error = dns_ns_parse(&ns, a, P))) glued[0] = !!dns_rr_grep(&x, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); if (!(error = dns_ns_parse(&ns, b, P))) glued[1] = !!dns_rr_grep(&y, 1, dns_rr_i_new(P, .section = (DNS_S_ALL & ~DNS_S_QD), .name = ns.host, .type = DNS_T_A), P, &error); if ((cmp = glued[1] - glued[0])) { return cmp; } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { return cmp; } else { return dns_rr_i_shuffle(a, b, i, P); } } /* dns_res_nameserv_cmp() */ #define dgoto(sp, i) \ do { R->stack[(sp)].state = (i); goto exec; } while (0) static int dns_res_exec(struct dns_resolver *R) { struct dns_res_frame *F; struct dns_packet *P; union { char host[DNS_D_MAXNAME + 1]; char name[DNS_D_MAXNAME + 1]; struct dns_ns ns; struct dns_cname cname; } u; size_t len; struct dns_rr rr; int error; exec: F = &R->stack[R->sp]; switch (F->state) { case DNS_R_INIT: F->state++; /* FALL THROUGH */ case DNS_R_GLUE: if (R->sp == 0) dgoto(R->sp, DNS_R_SWITCH); if (!F->query) goto noquery; if (!(F->answer = dns_res_glue(R, F->query))) dgoto(R->sp, DNS_R_SWITCH); if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) { dgoto(R->sp, DNS_R_FINISH); } dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } F->state++; case DNS_R_SWITCH: while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) { switch (R->resconf->lookup[F->which++]) { case 'b': case 'B': dgoto(R->sp, DNS_R_BIND); case 'f': case 'F': dgoto(R->sp, DNS_R_FILE); case 'c': case 'C': if (R->cache) dgoto(R->sp, DNS_R_CACHE); break; default: break; } } /* * FIXME: Examine more closely whether our logic is correct * and DNS_R_SERVFAIL is the correct default response. * * Case 1: We got here because we never got an answer on the * wire. All queries timed-out and we reached maximum * attempts count. See DNS_R_FOREACH_NS. In that case * DNS_R_SERVFAIL is the correct state, unless we want to * return DNS_ETIMEDOUT. * * Case 2: We were a stub resolver and got an unsatisfactory * answer (empty ANSWER section) which caused us to jump * back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We * return the answer returned from the wire, which we * stashed in R->nodata. * * Case 3: We reached maximum attempts count as in case #1, * but never got an authoritative response which caused us * to short-circuit. See end of DNS_R_QUERY_A case. We * should probably prepare R->nodata as in case #2. */ if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */ dns_p_movptr(&F->answer, &R->nodata); dgoto(R->sp, DNS_R_FINISH); } dgoto(R->sp, DNS_R_SERVFAIL); case DNS_R_FILE: if (R->sp > 0) { if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } else { R->search = 0; while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) { if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) goto error; if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); } } dgoto(R->sp, DNS_R_SWITCH); case DNS_R_CACHE: error = 0; if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags))) goto error; if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; F->state++; /* FALL THROUGH */ case DNS_R_SUBMIT: if ((error = R->cache->submit(F->query, R->cache))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_CHECK: if ((error = R->cache->check(R->cache))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_FETCH: error = 0; if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) { if (dns_p_count(F->answer, DNS_S_AN) > 0) dgoto(R->sp, DNS_R_FINISH); dns_p_setptr(&F->answer, NULL); dgoto(R->sp, DNS_R_SWITCH); } else if (error) goto error; dgoto(R->sp, DNS_R_SWITCH); case DNS_R_BIND: if (R->sp > 0) { if (!F->query) goto noquery; dgoto(R->sp, DNS_R_HINTS); } R->search = 0; F->state++; /* FALL THROUGH */ case DNS_R_SEARCH: /* * XXX: We probably should only apply the domain search * algorithm if R->sp == 0. */ if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) dgoto(R->sp, DNS_R_SWITCH); if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_HINTS: if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error))) goto error; F->state++; /* FALL THROUGH */ case DNS_R_ITERATE: dns_rr_i_init(&F->hints_i, F->hints); F->hints_i.section = DNS_S_AUTHORITY; F->hints_i.type = DNS_T_NS; F->hints_i.sort = &dns_res_nameserv_cmp; F->hints_i.args[0] = F->hints->end; F->state++; /* FALL THROUGH */ case DNS_R_FOREACH_NS: dns_rr_i_save(&F->hints_i); /* Load our next nameserver host. */ if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) { if (++F->attempts < R->resconf->options.attempts) dgoto(R->sp, DNS_R_ITERATE); dgoto(R->sp, DNS_R_SWITCH); } dns_rr_i_init(&F->hints_j, F->hints); /* Assume there are glue records */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_RESOLV0_NS: /* Have we reached our max depth? */ if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FOREACH_NS); if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_RESOLV1_NS: if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error))) goto error; else if (len >= sizeof u.host) goto toolong; dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) { rr.section = DNS_S_AR; if ((error = dns_rr_copy(F->hints, &rr, F[1].answer))) goto error; dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */ } dgoto(R->sp, DNS_R_FOREACH_NS); case DNS_R_FOREACH_A: { struct dns_a a; struct sockaddr_in sin; /* * NOTE: Iterator initialized in DNS_R_FOREACH_NS because * this state is re-entrant, but we need to reset * .name to a valid pointer each time. */ if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv6 address. */ dns_rr_i_init(&F->hints_j, F->hints); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ dns_rr_i_init(&F->hints_j, F->hints); dgoto(R->sp, DNS_R_FOREACH_AAAA); } dgoto(R->sp, DNS_R_RESOLV0_NS); } dgoto(R->sp, DNS_R_FOREACH_NS); } if ((error = dns_a_parse(&a, &rr, F->hints))) goto error; memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */ sin.sin_family = AF_INET; sin.sin_addr = a.addr; if (R->sp == 0) sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr); else sin.sin_port = htons(53); if (DNS_DEBUG) { char addr[INET_ADDRSTRLEN + 1]; dns_a_print(addr, sizeof addr, &a); dns_header(F->query)->qid = dns_so_mkqid(&R->so); DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); } dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin); if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin))) goto error; F->state++; } /* FALL THROUGH */ case DNS_R_QUERY_A: if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) dgoto(R->sp, DNS_R_FOREACH_A); - if ((error = dns_so_check(&R->so))) + error = dns_so_check(&R->so); + if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED) + dgoto(R->sp, DNS_R_FOREACH_A); + else if (error) goto error; if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) goto error; if (DNS_DEBUG) { DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); } if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || dns_p_rcode(F->answer) == DNS_RC_NOTIMP || dns_p_rcode(F->answer) == DNS_RC_BADVERS) { /* Temporarily disable EDNS0 and try again. */ if (F->qflags & DNS_Q_EDNS0) { F->qflags &= ~DNS_Q_EDNS0; if ((error = dns_q_remake(&F->query, F->qflags))) goto error; dgoto(R->sp, DNS_R_FOREACH_A); } } if ((error = dns_rr_parse(&rr, 12, F->query))) goto error; if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { dgoto(R->sp, DNS_R_FINISH); /* Found */ } dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } /* * XXX: The condition here should probably check whether * R->sp == 0, because DNS_R_SEARCH runs regardless of * options.recurse. See DNS_R_BIND. */ if (!R->resconf->options.recurse) { /* Make first answer our tentative answer */ if (!R->nodata) dns_p_movptr(&R->nodata, &F->answer); dgoto(R->sp, DNS_R_SEARCH); } dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { dns_p_movptr(&F->hints, &F->answer); dgoto(R->sp, DNS_R_ITERATE); } /* XXX: Should this go further up? */ if (dns_header(F->answer)->aa) dgoto(R->sp, DNS_R_FINISH); /* XXX: Should we copy F->answer to R->nodata? */ dgoto(R->sp, DNS_R_FOREACH_A); case DNS_R_FOREACH_AAAA: { struct dns_aaaa aaaa; struct sockaddr_in6 sin6; /* * NOTE: Iterator initialized in DNS_R_FOREACH_NS because * this state is re-entrant, but we need to reset * .name to a valid pointer each time. */ if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) goto error; F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_AAAA; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { if (!dns_rr_i_count(&F->hints_j)) { /* Check if we have in fact servers with an IPv4 address. */ dns_rr_i_init(&F->hints_j, F->hints); F->hints_j.name = u.ns.host; F->hints_j.type = DNS_T_A; F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { /* We do. Reinitialize iterator and handle it. */ dns_rr_i_init(&F->hints_j, F->hints); dgoto(R->sp, DNS_R_FOREACH_A); } dgoto(R->sp, DNS_R_RESOLV0_NS); } dgoto(R->sp, DNS_R_FOREACH_NS); } if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints))) goto error; memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */ sin6.sin6_family = AF_INET6; sin6.sin6_addr = aaaa.addr; if (R->sp == 0) sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr); else sin6.sin6_port = htons(53); if (DNS_DEBUG) { char addr[INET6_ADDRSTRLEN + 1]; dns_aaaa_print(addr, sizeof addr, &aaaa); dns_header(F->query)->qid = dns_so_mkqid(&R->so); DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); } dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6); if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6))) goto error; F->state++; } /* FALL THROUGH */ case DNS_R_QUERY_AAAA: if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) dgoto(R->sp, DNS_R_FOREACH_AAAA); - if ((error = dns_so_check(&R->so))) + error = dns_so_check(&R->so); + if (error == ECONNREFUSED) + dgoto(R->sp, DNS_R_FOREACH_AAAA); + else if (error) goto error; if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) goto error; if (DNS_DEBUG) { DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); } if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || dns_p_rcode(F->answer) == DNS_RC_NOTIMP || dns_p_rcode(F->answer) == DNS_RC_BADVERS) { /* Temporarily disable EDNS0 and try again. */ if (F->qflags & DNS_Q_EDNS0) { F->qflags &= ~DNS_Q_EDNS0; if ((error = dns_q_remake(&F->query, F->qflags))) goto error; dgoto(R->sp, DNS_R_FOREACH_AAAA); } } if ((error = dns_rr_parse(&rr, 12, F->query))) goto error; if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) goto error; else if (len >= sizeof u.name) goto toolong; dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { dgoto(R->sp, DNS_R_FINISH); /* Found */ } dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { F->ans_cname = rr; dgoto(R->sp, DNS_R_CNAME0_A); } /* * XXX: The condition here should probably check whether * R->sp == 0, because DNS_R_SEARCH runs regardless of * options.recurse. See DNS_R_BIND. */ if (!R->resconf->options.recurse) { /* Make first answer our tentative answer */ if (!R->nodata) dns_p_movptr(&R->nodata, &F->answer); dgoto(R->sp, DNS_R_SEARCH); } dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { dns_p_movptr(&F->hints, &F->answer); dgoto(R->sp, DNS_R_ITERATE); } /* XXX: Should this go further up? */ if (dns_header(F->answer)->aa) dgoto(R->sp, DNS_R_FINISH); /* XXX: Should we copy F->answer to R->nodata? */ dgoto(R->sp, DNS_R_FOREACH_AAAA); case DNS_R_CNAME0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_FINISH); if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer))) goto error; if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); case DNS_R_CNAME1_A: if (!(P = dns_res_merge(F->answer, F[1].answer, &error))) goto error; dns_p_setptr(&F->answer, P); dgoto(R->sp, DNS_R_FINISH); case DNS_R_FINISH: if (!F->answer) goto noanswer; if (!R->resconf->options.smart || R->sp > 0) dgoto(R->sp, DNS_R_DONE); R->smart.section = DNS_S_AN; R->smart.type = R->qtype; dns_rr_i_init(&R->smart, F->answer); F->state++; /* FALL THROUGH */ case DNS_R_SMART0_A: if (&F[1] >= endof(R->stack)) dgoto(R->sp, DNS_R_DONE); while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) { union { struct dns_ns ns; struct dns_mx mx; struct dns_srv srv; } rd; const char *qname; enum dns_type qtype; enum dns_class qclass; switch (rr.type) { case DNS_T_NS: if ((error = dns_ns_parse(&rd.ns, &rr, F->answer))) goto error; qname = rd.ns.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_MX: if ((error = dns_mx_parse(&rd.mx, &rr, F->answer))) goto error; qname = rd.mx.host; qtype = DNS_T_A; qclass = DNS_C_IN; break; case DNS_T_SRV: if ((error = dns_srv_parse(&rd.srv, &rr, F->answer))) goto error; qname = rd.srv.target; qtype = DNS_T_A; qclass = DNS_C_IN; break; default: continue; } /* switch() */ if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass))) goto error; F->state++; dgoto(++R->sp, DNS_R_INIT); } /* while() */ /* * NOTE: SMTP specification says to fallback to A record. * * XXX: Should we add a mock MX answer? */ if (R->qtype == DNS_T_MX && R->smart.state.count == 0) { if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN))) goto error; R->smart.state.count++; F->state++; dgoto(++R->sp, DNS_R_INIT); } dgoto(R->sp, DNS_R_DONE); case DNS_R_SMART1_A: if (!F[1].answer) goto noanswer; /* * FIXME: For CNAME chains (which are typically illegal in * this context), we should rewrite the record host name * to the original smart qname. All the user cares about * is locating that A/AAAA record. */ dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) { rr.section = DNS_S_AR; if (dns_rr_exists(&rr, F[1].answer, F->answer)) continue; while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) { if (error != DNS_ENOBUFS) goto error; if ((error = dns_p_grow(&F->answer))) goto error; } } dgoto(R->sp, DNS_R_SMART0_A); case DNS_R_DONE: if (!F->answer) goto noanswer; if (R->sp > 0) dgoto(--R->sp, F[-1].state); break; case DNS_R_SERVFAIL: if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error))) goto error; dns_header(F->answer)->qr = 1; dns_header(F->answer)->rcode = DNS_RC_SERVFAIL; if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0))) goto error; dgoto(R->sp, DNS_R_DONE); default: error = EINVAL; goto error; } /* switch () */ return 0; noquery: error = DNS_ENOQUERY; goto error; noanswer: error = DNS_ENOANSWER; goto error; toolong: error = DNS_EILLEGAL; /* FALL THROUGH */ error: return error; } /* dns_res_exec() */ #undef goto void dns_res_clear(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: R->cache->clear(R->cache); break; default: dns_so_clear(&R->so); break; } } /* dns_res_clear() */ static int dns_res_events2(struct dns_resolver *R, enum dns_events type) { int events; switch (R->stack[R->sp].state) { case DNS_R_CHECK: events = R->cache->events(R->cache); return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events; default: return dns_so_events2(&R->so, type); } } /* dns_res_events2() */ int dns_res_events(struct dns_resolver *R) { return dns_res_events2(R, R->so.opts.events); } /* dns_res_events() */ int dns_res_pollfd(struct dns_resolver *R) { switch (R->stack[R->sp].state) { case DNS_R_CHECK: return R->cache->pollfd(R->cache); default: return dns_so_pollfd(&R->so); } } /* dns_res_pollfd() */ time_t dns_res_timeout(struct dns_resolver *R) { time_t elapsed; switch (R->stack[R->sp].state) { #if 0 case DNS_R_QUERY_AAAA: #endif case DNS_R_QUERY_A: elapsed = dns_so_elapsed(&R->so); if (elapsed <= dns_resconf_timeout(R->resconf)) return R->resconf->options.timeout - elapsed; break; default: break; } /* switch() */ /* * NOTE: We're not in a pollable state, or the user code hasn't * called dns_res_check properly. The calling code is probably * broken. Put them into a slow-burn pattern. */ return 1; } /* dns_res_timeout() */ time_t dns_res_elapsed(struct dns_resolver *R) { return dns_elapsed(&R->elapsed); } /* dns_res_elapsed() */ int dns_res_poll(struct dns_resolver *R, int timeout) { return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout); } /* dns_res_poll() */ int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) { dns_res_reset(R); /* Don't anchor; that can conflict with searchlist generation. */ dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0); R->qtype = qtype; R->qclass = qclass; dns_begin(&R->elapsed); dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0); return 0; } /* dns_res_submit2() */ int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) { return dns_res_submit2(R, qname, strlen(qname), qtype, qclass); } /* dns_res_submit() */ int dns_res_check(struct dns_resolver *R) { int error; if (R->stack[0].state != DNS_R_DONE) { if ((error = dns_res_exec(R))) return error; } return 0; } /* dns_res_check() */ struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (R->stack[0].state != DNS_R_DONE) { error = DNS_EUNKNOWN; goto error; } if (!dns_p_movptr(&P, &R->stack[0].answer)) { error = DNS_EFETCHED; goto error; } dns_trace_res_fetch(R->trace, P, 0); return P; error: *_error = error; dns_trace_res_fetch(R->trace, NULL, error); return NULL; } /* dns_res_fetch() */ static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) { struct dns_packet *P = NULL; int error; if (!(P = dns_res_fetch(R, &error))) goto error; if ((error = dns_p_study(P))) goto error; return P; error: *_error = error; dns_p_free(P); return NULL; } /* dns_res_fetch_and_study() */ struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) { int error; if ((error = dns_res_submit(res, qname, qtype, qclass))) goto error; while ((error = dns_res_check(res))) { if (dns_res_elapsed(res) > timeout) error = DNS_ETIMEDOUT; if (error != DNS_EAGAIN) goto error; if ((error = dns_res_poll(res, 1))) goto error; } return dns_res_fetch(res, error_); error: *error_ = error; return 0; } /* dns_res_query() */ const struct dns_stat *dns_res_stat(struct dns_resolver *res) { return dns_so_stat(&res->so); } /* dns_res_stat() */ void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) { dns_hints_acquire(hints); /* acquire first in case same hints object */ dns_hints_close(res->hints); res->hints = hints; } /* dns_res_sethints() */ struct dns_trace *dns_res_trace(struct dns_resolver *res) { return res->trace; } /* dns_res_trace() */ void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) { struct dns_trace *otrace = res->trace; res->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); dns_so_settrace(&res->so, trace); } /* dns_res_settrace() */ /* * A D D R I N F O R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct dns_addrinfo { struct addrinfo hints; struct dns_resolver *res; struct dns_trace *trace; char qname[DNS_D_MAXNAME + 1]; enum dns_type qtype; unsigned short qport, port; struct { unsigned long todo; int state; int atype; enum dns_type qtype; } af; struct dns_packet *answer; struct dns_packet *glue; struct dns_rr_i i, g; struct dns_rr rr; char cname[DNS_D_MAXNAME + 1]; char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1]; int g_depth; int state; int found; struct dns_stat st; }; /* struct dns_addrinfo */ #define DNS_AI_AFMAX 32 #define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1)) static inline unsigned long dns_ai_af2index(int af) { dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch"); dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch"); return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0; } static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) { ai->af.atype = af; ai->af.qtype = qtype; ai->af.todo &= ~dns_ai_af2index(af); return af; } /* dns_ai_setaf() */ #define DNS_SM_RESTORE \ do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0) #define DNS_SM_SAVE \ do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0) static int dns_ai_nextaf(struct dns_addrinfo *ai) { int i, pc; dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0"); dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large"); dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large"); DNS_SM_ENTER; if (ai->res) { /* * NB: On OpenBSD, at least, the types of entries resolved * is the intersection of the /etc/resolv.conf families and * the families permitted by the .ai_type hint. So if * /etc/resolv.conf has "family inet4" and .ai_type * is AF_INET6, then the address ::1 will return 0 entries * even if AI_NUMERICHOST is specified in .ai_flags. */ while (i < (int)lengthof(ai->res->resconf->family)) { int af = ai->res->resconf->family[i++]; if (af == AF_UNSPEC) { DNS_SM_EXIT; } else if (af < 0 || af > DNS_AI_AFMAX) { continue; } else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) { continue; } else if (af == AF_INET) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); } else if (af == AF_INET6) { DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } } } else { /* * NB: If we get here than AI_NUMERICFLAGS should be set and * order shouldn't matter. */ if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo) DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); } DNS_SM_LEAVE; return dns_ai_setaf(ai, AF_UNSPEC, 0); } /* dns_ai_nextaf() */ #undef DNS_SM_RESTORE #undef DNS_SM_SAVE static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) { return (ai->qtype)? ai->qtype : ai->af.qtype; } /* dns_ai_qtype() */ /* JW: This is not defined on mingw. */ #ifndef AI_NUMERICSERV #define AI_NUMERICSERV 0 #endif static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) { const char *cp = serv; unsigned long n = 0; while (*cp >= '0' && *cp <= '9' && n < 65536) { n *= 10; n += *cp++ - '0'; } if (*cp == '\0') { if (cp == serv || n >= 65536) return DNS_ESERVICE; *port = n; return 0; } if (hints->ai_flags & AI_NUMERICSERV) return DNS_ESERVICE; /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */ return DNS_ESERVICE; } /* dns_ai_parseport() */ struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) { static const struct dns_addrinfo ai_initializer; struct dns_addrinfo *ai; int error; if (res) { dns_res_acquire(res); } else if (!(hints->ai_flags & AI_NUMERICHOST)) { /* * NOTE: it's assumed that *_error is set from a previous * API function call, such as dns_res_stub(). Should change * this semantic, but it's applied elsewhere, too. */ if (!*_error) *_error = EINVAL; return NULL; } if (!(ai = malloc(sizeof *ai))) goto syerr; *ai = ai_initializer; ai->hints = *hints; ai->res = res; res = NULL; if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname)) { error = ENAMETOOLONG; goto error; } ai->qtype = qtype; ai->qport = 0; if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints))) goto error; ai->port = ai->qport; /* * FIXME: If an explicit A or AAAA record type conflicts with * .ai_family or with resconf.family (i.e. AAAA specified but * AF_INET6 not in interection of .ai_family and resconf.family), * then what? */ switch (ai->qtype) { case DNS_T_A: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case DNS_T_AAAA: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: /* 0, MX, SRV, etc */ switch (ai->hints.ai_family) { case AF_UNSPEC: ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6); break; case AF_INET: ai->af.todo = DNS_AI_AF2INDEX(AF_INET); break; case AF_INET6: ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); break; default: break; } } return ai; syerr: error = dns_syerr(); error: *_error = error; dns_ai_close(ai); dns_res_close(res); return NULL; } /* dns_ai_open() */ void dns_ai_close(struct dns_addrinfo *ai) { if (!ai) return; dns_res_close(ai->res); dns_trace_close(ai->trace); if (ai->answer != ai->glue) dns_p_free(ai->glue); dns_p_free(ai->answer); free(ai); } /* dns_ai_close() */ static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) { union u { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr_storage ss; } addr; const char *cname; size_t clen; switch (type) { case DNS_T_A: memset(&addr.sin, '\0', sizeof addr.sin); addr.sin.sin_family = AF_INET; addr.sin.sin_port = htons(ai->port); memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr); break; case DNS_T_AAAA: memset(&addr.sin6, '\0', sizeof addr.sin6); addr.sin6.sin6_family = AF_INET6; addr.sin6.sin6_port = htons(ai->port); memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr); break; default: return EINVAL; } /* switch() */ if (ai->hints.ai_flags & AI_CANONNAME) { cname = (*ai->cname)? ai->cname : ai->qname; clen = strlen(cname); } else { cname = NULL; clen = 0; } if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0)))) return dns_syerr(); memset(*ent, '\0', sizeof **ent); (*ent)->ai_family = addr.ss.ss_family; (*ent)->ai_socktype = ai->hints.ai_socktype; (*ent)->ai_protocol = ai->hints.ai_protocol; (*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr)); (*ent)->ai_addrlen = dns_sa_len(&addr); if (ai->hints.ai_flags & AI_CANONNAME) (*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1); ai->found++; return 0; } /* dns_ai_setent() */ enum dns_ai_state { DNS_AI_S_INIT, DNS_AI_S_NEXTAF, DNS_AI_S_NUMERIC, DNS_AI_S_SUBMIT, DNS_AI_S_CHECK, DNS_AI_S_FETCH, DNS_AI_S_FOREACH_I, DNS_AI_S_INIT_G, DNS_AI_S_ITERATE_G, DNS_AI_S_FOREACH_G, DNS_AI_S_SUBMIT_G, DNS_AI_S_CHECK_G, DNS_AI_S_FETCH_G, DNS_AI_S_DONE, }; /* enum dns_ai_state */ #define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0) int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) { struct dns_packet *ans, *glue; struct dns_rr rr; char qname[DNS_D_MAXNAME + 1]; union dns_any any; size_t qlen, clen; int error; *ent = 0; exec: switch (ai->state) { case DNS_AI_S_INIT: ai->state++; /* FALL THROUGH */ case DNS_AI_S_NEXTAF: if (!dns_ai_nextaf(ai)) dns_ai_goto(DNS_AI_S_DONE); ai->state++; /* FALL THROUGH */ case DNS_AI_S_NUMERIC: if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) { if (ai->af.atype == AF_INET) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_A, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) { if (ai->af.atype == AF_INET6) { ai->state = DNS_AI_S_NEXTAF; return dns_ai_setent(ent, &any, DNS_T_AAAA, ai); } else { dns_ai_goto(DNS_AI_S_NEXTAF); } } if (ai->hints.ai_flags & AI_NUMERICHOST) dns_ai_goto(DNS_AI_S_NEXTAF); ai->state++; /* FALL THROUGH */ case DNS_AI_S_SUBMIT: assert(ai->res); if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_CHECK: if ((error = dns_res_check(ai->res))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FETCH: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = dns_p_movptr(&ai->answer, &ans); /* Search generator may have changed the qname. */ if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error))) return error; else if (qlen >= sizeof qname) return DNS_EILLEGAL; if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error)) return error; dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); dns_rr_i_init(&ai->i, ai->answer); ai->i.section = DNS_S_AN; ai->i.name = ai->i_cname; ai->i.type = dns_ai_qtype(ai); ai->i.sort = &dns_rr_i_order; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FOREACH_I: if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error)) dns_ai_goto(DNS_AI_S_NEXTAF); if ((error = dns_any_parse(&any, &rr, ai->answer))) return error; ai->port = ai->qport; switch (rr.type) { case DNS_T_A: case DNS_T_AAAA: return dns_ai_setent(ent, &any, rr.type, ai); default: if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type))) dns_ai_goto(DNS_AI_S_FOREACH_I); /* * Find the "real" canonical name. Some authorities * publish aliases where an RFC defines a canonical * name. We trust that the resolver followed any * CNAME chains on it's own, regardless of whether * the "smart" option is enabled. */ if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error)) return error; if (rr.type == DNS_T_SRV) ai->port = any.srv.port; break; } /* switch() */ ai->state++; /* FALL THROUGH */ case DNS_AI_S_INIT_G: ai->g_depth = 0; ai->state++; /* FALL THROUGH */ case DNS_AI_S_ITERATE_G: dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); dns_rr_i_init(&ai->g, ai->glue); ai->g.section = DNS_S_ALL & ~DNS_S_QD; ai->g.name = ai->g_cname; ai->g.type = ai->af.qtype; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FOREACH_G: if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) { if (dns_rr_i_count(&ai->g) > 0) dns_ai_goto(DNS_AI_S_FOREACH_I); else dns_ai_goto(DNS_AI_S_SUBMIT_G); } if ((error = dns_any_parse(&any, &rr, ai->glue))) return error; return dns_ai_setent(ent, &any, rr.type, ai); case DNS_AI_S_SUBMIT_G: /* skip if already queried */ if (dns_rr_grep(&rr, 1, dns_rr_i_new(ai->glue, .section = DNS_S_QD, .name = ai->g.name, .type = ai->g.type), ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); /* skip if we recursed (CNAME chains should have been handled in the resolver) */ if (++ai->g_depth > 1) dns_ai_goto(DNS_AI_S_FOREACH_I); if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_CHECK_G: if ((error = dns_res_check(ai->res))) return error; ai->state++; /* FALL THROUGH */ case DNS_AI_S_FETCH_G: if (!(ans = dns_res_fetch_and_study(ai->res, &error))) return error; glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error); dns_p_setptr(&ans, NULL); if (!glue) return error; if (ai->glue != ai->answer) dns_p_free(ai->glue); ai->glue = glue; if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error)) dns_ai_goto(DNS_AI_S_FOREACH_I); dns_ai_goto(DNS_AI_S_ITERATE_G); case DNS_AI_S_DONE: if (ai->found) { return ENOENT; /* TODO: Just return 0 */ } else if (ai->answer) { switch (dns_p_rcode(ai->answer)) { case DNS_RC_NOERROR: /* FALL THROUGH */ case DNS_RC_NXDOMAIN: return DNS_ENONAME; default: return DNS_EFAIL; } } else { return DNS_EFAIL; } default: return EINVAL; } /* switch() */ } /* dns_ai_nextent() */ time_t dns_ai_elapsed(struct dns_addrinfo *ai) { return (ai->res)? dns_res_elapsed(ai->res) : 0; } /* dns_ai_elapsed() */ void dns_ai_clear(struct dns_addrinfo *ai) { if (ai->res) dns_res_clear(ai->res); } /* dns_ai_clear() */ int dns_ai_events(struct dns_addrinfo *ai) { return (ai->res)? dns_res_events(ai->res) : 0; } /* dns_ai_events() */ int dns_ai_pollfd(struct dns_addrinfo *ai) { return (ai->res)? dns_res_pollfd(ai->res) : -1; } /* dns_ai_pollfd() */ time_t dns_ai_timeout(struct dns_addrinfo *ai) { return (ai->res)? dns_res_timeout(ai->res) : 0; } /* dns_ai_timeout() */ int dns_ai_poll(struct dns_addrinfo *ai, int timeout) { return (ai->res)? dns_res_poll(ai->res, timeout) : 0; } /* dns_ai_poll() */ size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) { struct dns_buf dst = DNS_B_INTO(_dst, lim); char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; dns_b_puts(&dst, "[ "); dns_b_puts(&dst, ai->qname); dns_b_puts(&dst, " IN "); if (ai->qtype) { dns_b_puts(&dst, dns_strtype(ai->qtype)); } else if (ent->ai_family == AF_INET) { dns_b_puts(&dst, dns_strtype(DNS_T_A)); } else if (ent->ai_family == AF_INET6) { dns_b_puts(&dst, dns_strtype(DNS_T_AAAA)); } else { dns_b_puts(&dst, "0"); } dns_b_puts(&dst, " ]\n"); dns_b_puts(&dst, ".ai_family = "); switch (ent->ai_family) { case AF_INET: dns_b_puts(&dst, "AF_INET"); break; case AF_INET6: dns_b_puts(&dst, "AF_INET6"); break; default: dns_b_fmtju(&dst, ent->ai_family, 0); break; } dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_socktype = "); switch (ent->ai_socktype) { case SOCK_STREAM: dns_b_puts(&dst, "SOCK_STREAM"); break; case SOCK_DGRAM: dns_b_puts(&dst, "SOCK_DGRAM"); break; default: dns_b_fmtju(&dst, ent->ai_socktype, 0); break; } dns_b_putc(&dst, '\n'); dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr); dns_b_puts(&dst, ".ai_addr = ["); dns_b_puts(&dst, addr); dns_b_puts(&dst, "]:"); dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0); dns_b_putc(&dst, '\n'); dns_b_puts(&dst, ".ai_canonname = "); dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]"); dns_b_putc(&dst, '\n'); return dns_b_strllen(&dst); } /* dns_ai_print() */ const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) { return (ai->res)? dns_res_stat(ai->res) : &ai->st; } /* dns_ai_stat() */ struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) { return ai->trace; } /* dns_ai_trace() */ void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) { struct dns_trace *otrace = ai->trace; ai->trace = dns_trace_acquire_p(trace); dns_trace_close(otrace); if (ai->res) dns_res_settrace(ai->res, trace); } /* dns_ai_settrace() */ /* * M I S C E L L A N E O U S R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ static const struct { char name[16]; enum dns_section type; } dns_sections[] = { { "QUESTION", DNS_S_QUESTION }, { "QD", DNS_S_QUESTION }, { "ANSWER", DNS_S_ANSWER }, { "AN", DNS_S_ANSWER }, { "AUTHORITY", DNS_S_AUTHORITY }, { "NS", DNS_S_AUTHORITY }, { "ADDITIONAL", DNS_S_ADDITIONAL }, { "AR", DNS_S_ADDITIONAL }, }; const char *(dns_strsection)(enum dns_section section, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_sections); i++) { if (dns_sections[i].type & section) { dns_b_puts(&dst, dns_sections[i].name); section &= ~dns_sections[i].type; if (section) dns_b_putc(&dst, '|'); } } if (section || dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & section), 0); return dns_b_tostring(&dst); } /* dns_strsection() */ enum dns_section dns_isection(const char *src) { enum dns_section section = 0; char sbuf[128]; char *name, *next; unsigned i; dns_strlcpy(sbuf, src, sizeof sbuf); next = sbuf; while ((name = dns_strsep(&next, "|+, \t"))) { for (i = 0; i < lengthof(dns_sections); i++) { if (!strcasecmp(dns_sections[i].name, name)) { section |= dns_sections[i].type; break; } } } return section; } /* dns_isection() */ static const struct { char name[8]; enum dns_class type; } dns_classes[] = { { "IN", DNS_C_IN }, }; const char *(dns_strclass)(enum dns_class type, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_classes); i++) { if (dns_classes[i].type == type) { dns_b_puts(&dst, dns_classes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strclass() */ enum dns_class dns_iclass(const char *name) { unsigned i, class; for (i = 0; i < lengthof(dns_classes); i++) { if (!strcasecmp(dns_classes[i].name, name)) return dns_classes[i].type; } class = 0; while (dns_isdigit(*name)) { class *= 10; class += *name++ - '0'; } return DNS_PP_MIN(class, 0xffff); } /* dns_iclass() */ const char *(dns_strtype)(enum dns_type type, void *_dst, size_t lim) { struct dns_buf dst = DNS_B_INTO(_dst, lim); unsigned i; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (dns_rrtypes[i].type == type) { dns_b_puts(&dst, dns_rrtypes[i].name); break; } } if (dst.p == dst.base) dns_b_fmtju(&dst, (0xffff & type), 0); return dns_b_tostring(&dst); } /* dns_strtype() */ enum dns_type dns_itype(const char *name) { unsigned i, type; for (i = 0; i < lengthof(dns_rrtypes); i++) { if (!strcasecmp(dns_rrtypes[i].name, name)) return dns_rrtypes[i].type; } type = 0; while (dns_isdigit(*name)) { type *= 10; type += *name++ - '0'; } return DNS_PP_MIN(type, 0xffff); } /* dns_itype() */ static char dns_opcodes[16][16] = { [DNS_OP_QUERY] = "QUERY", [DNS_OP_IQUERY] = "IQUERY", [DNS_OP_STATUS] = "STATUS", [DNS_OP_NOTIFY] = "NOTIFY", [DNS_OP_UPDATE] = "UPDATE", }; static const char *dns__strcode(int code, volatile char *dst, size_t lim) { char _tmp[48] = ""; struct dns_buf tmp; size_t p; assert(lim > 0); dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0); /* copy downwards so first byte is copied last (see below) */ p = (size_t)(tmp.p - tmp.base); dst[p] = '\0'; while (p--) dst[p] = _tmp[p]; return (const char *)dst; } const char *dns_stropcode(enum dns_opcode opcode) { opcode = (unsigned)opcode % lengthof(dns_opcodes); if ('\0' == dns_opcodes[opcode][0]) return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]); return dns_opcodes[opcode]; } /* dns_stropcode() */ enum dns_opcode dns_iopcode(const char *name) { unsigned opcode; for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) { if (!strcasecmp(name, dns_opcodes[opcode])) return opcode; } opcode = 0; while (dns_isdigit(*name)) { opcode *= 10; opcode += *name++ - '0'; } return DNS_PP_MIN(opcode, 0x0f); } /* dns_iopcode() */ static char dns_rcodes[32][16] = { [DNS_RC_NOERROR] = "NOERROR", [DNS_RC_FORMERR] = "FORMERR", [DNS_RC_SERVFAIL] = "SERVFAIL", [DNS_RC_NXDOMAIN] = "NXDOMAIN", [DNS_RC_NOTIMP] = "NOTIMP", [DNS_RC_REFUSED] = "REFUSED", [DNS_RC_YXDOMAIN] = "YXDOMAIN", [DNS_RC_YXRRSET] = "YXRRSET", [DNS_RC_NXRRSET] = "NXRRSET", [DNS_RC_NOTAUTH] = "NOTAUTH", [DNS_RC_NOTZONE] = "NOTZONE", /* EDNS(0) extended RCODEs ... */ [DNS_RC_BADVERS] = "BADVERS", }; const char *dns_strrcode(enum dns_rcode rcode) { rcode = (unsigned)rcode % lengthof(dns_rcodes); if ('\0' == dns_rcodes[rcode][0]) return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]); return dns_rcodes[rcode]; } /* dns_strrcode() */ enum dns_rcode dns_ircode(const char *name) { unsigned rcode; for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) { if (!strcasecmp(name, dns_rcodes[rcode])) return rcode; } rcode = 0; while (dns_isdigit(*name)) { rcode *= 10; rcode += *name++ - '0'; } return DNS_PP_MIN(rcode, 0xfff); } /* dns_ircode() */ /* * C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #if DNS_MAIN #include #include #include #include #if _WIN32 #include #endif #if !_WIN32 #include #endif struct { struct { const char *path[8]; unsigned count; } resconf, nssconf, hosts, cache; const char *qname; enum dns_type qtype; int (*sort)(); const char *trace; int verbose; struct { struct dns_resolv_conf *resconf; struct dns_hosts *hosts; struct dns_trace *trace; } memo; struct sockaddr_storage socks_host; const char *socks_user; const char *socks_password; } MAIN = { .sort = &dns_rr_i_packet, }; static void hexdump(const unsigned char *src, size_t len, FILE *fp) { struct dns_hxd_lines_i lines = { 0 }; char line[128]; while (dns_hxd_lines(line, sizeof line, src, len, &lines)) { fputs(line, fp); } } /* hexdump() */ DNS_NORETURN static void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); #if _WIN32 vfprintf(stderr, fmt, ap); exit(EXIT_FAILURE); #else verrx(EXIT_FAILURE, fmt, ap); #endif } /* panic() */ #define panic_(fn, ln, fmt, ...) \ panic(fmt "%0s", (fn), (ln), __VA_ARGS__) #define panic(...) \ panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "") static void *grow(unsigned char *p, size_t size) { void *tmp; if (!(tmp = realloc(p, size))) panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno)); return tmp; } /* grow() */ static size_t add(size_t a, size_t b) { if (~a < b) panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b); return a + b; } /* add() */ static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) { size_t size = add(osize, len); *dst = grow(*dst, size); memcpy(*dst + osize, src, len); return size; } /* append() */ static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) { size_t size = osize; unsigned char buf[1024]; size_t count; while ((count = fread(buf, 1, sizeof buf, fp))) size = append(dst, size, buf, count); if (ferror(fp)) panic("%s: %s", path, dns_strerror(errno)); return size; } /* slurp() */ static struct dns_resolv_conf *resconf(void) { struct dns_resolv_conf **resconf = &MAIN.memo.resconf; const char *path; unsigned i; int error; if (*resconf) return *resconf; if (!(*resconf = dns_resconf_open(&error))) panic("dns_resconf_open: %s", dns_strerror(error)); if (!MAIN.resconf.count) MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf"; for (i = 0; i < MAIN.resconf.count; i++) { path = MAIN.resconf.path[i]; if (0 == strcmp(path, "-")) error = dns_resconf_loadfile(*resconf, stdin); else error = dns_resconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } for (i = 0; i < MAIN.nssconf.count; i++) { path = MAIN.nssconf.path[i]; if (0 == strcmp(path, "-")) error = dns_nssconf_loadfile(*resconf, stdin); else error = dns_nssconf_loadpath(*resconf, path); if (error) panic("%s: %s", path, dns_strerror(error)); } if (!MAIN.nssconf.count) { path = "/etc/nsswitch.conf"; if (!(error = dns_nssconf_loadpath(*resconf, path))) MAIN.nssconf.path[MAIN.nssconf.count++] = path; else if (error != ENOENT) panic("%s: %s", path, dns_strerror(error)); } return *resconf; } /* resconf() */ static struct dns_hosts *hosts(void) { struct dns_hosts **hosts = &MAIN.memo.hosts; const char *path; unsigned i; int error; if (*hosts) return *hosts; if (!MAIN.hosts.count) { MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts"; /* Explicitly test dns_hosts_local() */ if (!(*hosts = dns_hosts_local(&error))) panic("%s: %s", "/etc/hosts", dns_strerror(error)); return *hosts; } if (!(*hosts = dns_hosts_open(&error))) panic("dns_hosts_open: %s", dns_strerror(error)); for (i = 0; i < MAIN.hosts.count; i++) { path = MAIN.hosts.path[i]; if (0 == strcmp(path, "-")) error = dns_hosts_loadfile(*hosts, stdin); else error = dns_hosts_loadpath(*hosts, path); if (error) panic("%s: %s", path, dns_strerror(error)); } return *hosts; } /* hosts() */ #if DNS_CACHE #include "cache.h" static struct dns_cache *cache(void) { static struct cache *cache; const char *path; unsigned i; int error; if (cache) return cache_resi(cache); if (!MAIN.cache.count) return NULL; if (!(cache = cache_open(&error))) panic("%s: %s", MAIN.cache.path[0], dns_strerror(error)); for (i = 0; i < MAIN.cache.count; i++) { path = MAIN.cache.path[i]; if (!strcmp(path, "-")) { if ((error = cache_loadfile(cache, stdin, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } else if ((error = cache_loadpath(cache, path, NULL, 0))) panic("%s: %s", path, dns_strerror(error)); } return cache_resi(cache); } /* cache() */ #else static struct dns_cache *cache(void) { return NULL; } #endif static struct dns_trace *trace(const char *mode) { static char omode[64] = ""; struct dns_trace **trace = &MAIN.memo.trace; FILE *fp; int error; if (*trace && 0 == strcmp(omode, mode)) return *trace; if (!MAIN.trace) return NULL; if (!(fp = fopen(MAIN.trace, mode))) panic("%s: %s", MAIN.trace, strerror(errno)); dns_trace_close(*trace); if (!(*trace = dns_trace_open(fp, &error))) panic("%s: %s", MAIN.trace, dns_strerror(error)); dns_strlcpy(omode, mode, sizeof omode); return *trace; } static void print_packet(struct dns_packet *P, FILE *fp) { dns_p_dump3(P, dns_rr_i_new(P, .sort = MAIN.sort), fp); if (MAIN.verbose > 2) hexdump(P->data, P->end, fp); } /* print_packet() */ static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { struct dns_packet *P = dns_p_new(512); struct dns_packet *Q = dns_p_new(512); enum dns_section section; struct dns_rr rr; int error; union dns_any any; char pretty[sizeof any * 2]; size_t len; P->end = fread(P->data, 1, P->size, stdin); fputs(";; [HEADER]\n", stdout); fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); section = 0; dns_rr_foreach(&rr, P, .sort = MAIN.sort) { if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) fprintf(stdout, "%s\n", pretty); dns_rr_copy(Q, &rr, P); section = rr.section; } fputs("; ; ; ; ; ; ; ;\n\n", stdout); section = 0; #if 0 dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { #else struct dns_rr rrset[32]; struct dns_rr_i *rri = dns_rr_i_new(Q, .name = dns_d_new("ns8.yahoo.com", DNS_D_ANCHOR), .sort = MAIN.sort); unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); for (unsigned i = 0; i < rrcount; i++) { rr = rrset[i]; #endif if (section != rr.section) fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(Q, rr.section)); if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error))) fprintf(stdout, "%s\n", pretty); section = rr.section; } if (MAIN.verbose > 1) { fprintf(stderr, "orig:%"PRIuZ"\n", P->end); hexdump(P->data, P->end, stdout); fprintf(stderr, "copy:%"PRIuZ"\n", Q->end); hexdump(Q->data, Q->end, stdout); } return 0; } /* parse_packet() */ static int parse_domain(int argc, char *argv[]) { char *dn; dn = (argc > 1)? argv[1] : "f.l.google.com"; printf("[%s]\n", dn); dn = dns_d_new(dn); do { puts(dn); } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn))); return 0; } /* parse_domain() */ static int trim_domain(int argc, char **argv) { for (argc--, argv++; argc > 0; argc--, argv++) { char name[DNS_D_MAXNAME + 1]; dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR); puts(name); } return 0; } /* trim_domain() */ static int expand_domain(int argc, char *argv[]) { unsigned short rp = 0; unsigned char *src = NULL; unsigned char *dst; struct dns_packet *pkt; size_t lim = 0, len; int error; if (argc > 1) rp = atoi(argv[1]); len = slurp(&src, 0, stdin, "-"); if (!(pkt = dns_p_make(len, &error))) panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error)); memcpy(pkt->data, src, len); pkt->end = len; lim = 1; dst = grow(NULL, lim); while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) { lim = add(len, 1); dst = grow(dst, lim); } if (!len) panic("expand: %s", dns_strerror(error)); fwrite(dst, 1, len, stdout); fflush(stdout); free(src); free(dst); free(pkt); return 0; } /* expand_domain() */ static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); /* load it */ fputs("; SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "; %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]); fputs(";\n", stdout); dns_resconf_dump(resconf(), stdout); return 0; } /* show_resconf() */ static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; resconf(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.resconf.count; i++) fprintf(stdout, "# %s\n", MAIN.resconf.path[i]); for (i = 0; i < MAIN.nssconf.count; i++) fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]); fputs("#\n", stdout); dns_nssconf_dump(resconf(), stdout); return 0; } /* show_nssconf() */ static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { unsigned i; hosts(); fputs("# SOURCES\n", stdout); for (i = 0; i < MAIN.hosts.count; i++) fprintf(stdout, "# %s\n", MAIN.hosts.path[i]); fputs("#\n", stdout); dns_hosts_dump(hosts(), stdout); return 0; } /* show_hosts() */ static int query_hosts(int argc, char *argv[]) { struct dns_packet *Q = dns_p_new(512); struct dns_packet *A; char qname[DNS_D_MAXNAME + 1]; size_t qlen; int error; if (!MAIN.qname) MAIN.qname = (argc > 1)? argv[1] : "localhost"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; hosts(); if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) { union { struct in_addr a; struct in6_addr a6; } addr; int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(af, MAIN.qname, &addr))) panic("%s: %s", MAIN.qname, dns_strerror(error)); qlen = dns_ptr_qname(qname, sizeof qname, af, &addr); } else qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname); if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0))) panic("%s: %s", qname, dns_strerror(error)); if (!(A = dns_hosts_query(hosts(), Q, &error))) panic("%s: %s", qname, dns_strerror(error)); print_packet(A, stdout); free(A); return 0; } /* query_hosts() */ static int search_list(int argc, char *argv[]) { const char *qname = (argc > 1)? argv[1] : "f.l.google.com"; unsigned long i = 0; char name[DNS_D_MAXNAME + 1]; printf("[%s]\n", qname); while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i)) puts(name); return 0; } /* search_list() */ static int permute_set(int argc, char *argv[]) { unsigned lo, hi, i; struct dns_k_permutor p; hi = (--argc > 0)? atoi(argv[argc]) : 8; lo = (--argc > 0)? atoi(argv[argc]) : 0; fprintf(stderr, "[%u .. %u]\n", lo, hi); dns_k_permutor_init(&p, lo, hi); for (i = lo; i <= hi; i++) fprintf(stdout, "%u\n", dns_k_permutor_step(&p)); // printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i))); return 0; } /* permute_set() */ static int shuffle_16(int argc, char *argv[]) { unsigned n, r; if (--argc > 0) { n = 0xffff & atoi(argv[argc]); r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random(); fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } else { r = dns_random(); for (n = 0; n < 65536; n++) fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); } return 0; } /* shuffle_16() */ static int dump_random(int argc, char *argv[]) { unsigned char b[32]; unsigned i, j, n, r; n = (argc > 1)? atoi(argv[1]) : 32; while (n) { i = 0; do { r = dns_random(); for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) { b[i] = 0xff & r; r >>= 8; } } while (i < n && i < sizeof b); hexdump(b, i, stdout); n -= i; } return 0; } /* dump_random() */ static int send_query(int argc, char *argv[]) { struct dns_packet *A, *Q = dns_p_new(512); char host[INET6_ADDRSTRLEN + 1]; struct sockaddr_storage ss; struct dns_socket *so; int error, type; + memset(&ss, 0, sizeof ss); if (argc > 1) { ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET; if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL)))) panic("%s: %s", argv[1], dns_strerror(error)); *dns_sa_port(ss.ss_family, &ss) = htons(53); } else memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0])); if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host)) panic("bad host address, or none provided"); if (!MAIN.qname) MAIN.qname = "ipv6.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_AAAA; if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0))) panic("dns_p_push: %s", dns_strerror(error)); dns_header(Q)->rd = 1; if (strstr(argv[0], "udp")) type = SOCK_DGRAM; else if (strstr(argv[0], "tcp")) type = SOCK_STREAM; else type = dns_res_tcp2type(resconf()->options.tcp); fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype)); if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, dns_opts(), &error))) panic("dns_so_open: %s", dns_strerror(error)); while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { if (error != DNS_EAGAIN) panic("dns_so_query: %s (%d)", dns_strerror(error), error); if (dns_so_elapsed(so) > 10) panic("query timed-out"); dns_so_poll(so, 1); } print_packet(A, stdout); dns_so_close(so); return 0; } /* send_query() */ static int print_arpa(int argc, char *argv[]) { const char *ip = (argc > 1)? argv[1] : "::1"; int af = (strchr(ip, ':'))? AF_INET6 : AF_INET; union { struct in_addr a4; struct in6_addr a6; } addr; char host[DNS_D_MAXNAME + 1]; if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr)) panic("%s: invalid address", ip); fprintf(stdout, "%s\n", host); return 0; } /* print_arpa() */ static int show_hints(int argc, char *argv[]) { struct dns_hints *(*load)(struct dns_resolv_conf *, int *); const char *which, *how, *who; struct dns_hints *hints; int error; which = (argc > 1)? argv[1] : "local"; how = (argc > 2)? argv[2] : "plain"; who = (argc > 3)? argv[3] : "google.com"; load = (0 == strcmp(which, "local")) ? &dns_hints_local : &dns_hints_root; if (!(hints = load(resconf(), &error))) panic("%s: %s", argv[0], dns_strerror(error)); if (0 == strcmp(how, "plain")) { dns_hints_dump(hints, stdout); } else { struct dns_packet *query, *answer; query = dns_p_new(512); if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) panic("%s: %s", who, dns_strerror(error)); if (!(answer = dns_hints_query(hints, query, &error))) panic("%s: %s", who, dns_strerror(error)); print_packet(answer, stdout); free(answer); } dns_hints_close(hints); return 0; } /* show_hints() */ static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *R; struct dns_packet *ans; const struct dns_stat *st; int error; if (!MAIN.qname) MAIN.qname = "www.google.com"; if (!MAIN.qtype) MAIN.qtype = DNS_T_A; resconf()->options.recurse = recurse; if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(.socks_host=&MAIN.socks_host, .socks_user=MAIN.socks_user, .socks_password=MAIN.socks_password), &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_res_settrace(R, trace("w+b")); if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN))) panic("%s: %s", MAIN.qname, dns_strerror(error)); while ((error = dns_res_check(R))) { if (error != DNS_EAGAIN) panic("dns_res_check: %s (%d)", dns_strerror(error), error); if (dns_res_elapsed(R) > 30) panic("query timed-out"); dns_res_poll(R, 1); } ans = dns_res_fetch(R, &error); print_packet(ans, stdout); free(ans); st = dns_res_stat(R); putchar('\n'); printf(";; queries: %"PRIuZ"\n", st->queries); printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes); printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes); printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes); printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes); dns_res_close(R); return 0; } /* resolve_query() */ static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { _Bool recurse = !!strstr(argv[0], "recurse"); struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; struct dns_resolver *res = NULL; struct dns_addrinfo *ai = NULL; struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; struct addrinfo *ent; char pretty[512]; int error; if (!MAIN.qname) MAIN.qname = "www.google.com"; /* NB: MAIN.qtype of 0 means obey hints.ai_family */ resconf()->options.recurse = recurse; if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), dns_opts(), &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) panic("%s: %s", MAIN.qname, dns_strerror(error)); dns_ai_settrace(ai, trace("w+b")); do { switch (error = dns_ai_nextent(&ent, ai)) { case 0: dns_ai_print(pretty, sizeof pretty, ent, ai); fputs(pretty, stdout); free(ent); break; case ENOENT: break; case DNS_EAGAIN: if (dns_ai_elapsed(ai) > 30) panic("query timed-out"); dns_ai_poll(ai, 1); break; default: panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error); } } while (error != ENOENT); dns_res_close(res); dns_ai_close(ai); return 0; } /* resolve_addrinfo() */ static int dump_trace(int argc DNS_NOTUSED, char *argv[]) { int error; if (!MAIN.trace) panic("no trace file specified"); if ((error = dns_trace_dump(trace("r"), stdout))) panic("dump_trace: %s", dns_strerror(error)); return 0; } /* dump_trace() */ static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { union { struct sockaddr sa; struct sockaddr_in sin; } port; int fd; memset(&port, 0, sizeof port); port.sin.sin_family = AF_INET; port.sin.sin_port = htons(5354); port.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0))) panic("socket: %s", strerror(errno)); if (0 != bind(fd, &port.sa, sizeof port.sa)) panic("127.0.0.1:5353: %s", dns_strerror(errno)); for (;;) { struct dns_packet *pkt = dns_p_new(512); struct sockaddr_storage ss; socklen_t slen = sizeof ss; ssize_t count; #if defined(MSG_WAITALL) /* MinGW issue */ int rflags = MSG_WAITALL; #else int rflags = 0; #endif count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen); if (!count || count < 0) panic("recvfrom: %s", strerror(errno)); pkt->end = count; dns_p_dump(pkt, stdout); (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen); } return 0; } /* echo_port() */ static int isection(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_isection(name); name = dns_strsection(type); printf("%s (%d)\n", name, type); return 0; } /* isection() */ static int iclass(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iclass(name); name = dns_strclass(type); printf("%s (%d)\n", name, type); return 0; } /* iclass() */ static int itype(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_itype(name); name = dns_strtype(type); printf("%s (%d)\n", name, type); return 0; } /* itype() */ static int iopcode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_iopcode(name); name = dns_stropcode(type); printf("%s (%d)\n", name, type); return 0; } /* iopcode() */ static int ircode(int argc, char *argv[]) { const char *name = (argc > 1)? argv[1] : ""; int type; type = dns_ircode(name); name = dns_strrcode(type); printf("%s (%d)\n", name, type); return 0; } /* ircode() */ #define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) } #define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__) #define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__) #define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__) #define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { static const struct { const char *name; size_t size; } type[] = { SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i), SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns), SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv), SIZE(struct dns_sshfp, struct dns_txt, union dns_any), SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i), SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo), SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long) }; unsigned i, max; for (i = 0, max = 0; i < lengthof(type); i++) max = DNS_PP_MAX(max, strlen(type[i].name)); for (i = 0; i < lengthof(type); i++) printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size); return 0; } /* sizes() */ static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = { { "parse-packet", &parse_packet, "parse binary packet from stdin" }, { "parse-domain", &parse_domain, "anchor and iteratively cleave domain" }, { "trim-domain", &trim_domain, "trim and anchor domain name" }, { "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" }, { "show-resconf", &show_resconf, "show resolv.conf data" }, { "show-hosts", &show_hosts, "show hosts data" }, { "show-nssconf", &show_nssconf, "show nsswitch.conf data" }, { "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" }, { "search-list", &search_list, "generate query search list from domain" }, { "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" }, { "shuffle-16", &shuffle_16, "simple 16-bit permutation" }, { "dump-random", &dump_random, "generate random bytes" }, { "send-query", &send_query, "send query to host" }, { "send-query-udp", &send_query, "send udp query to host" }, { "send-query-tcp", &send_query, "send tcp query to host" }, { "print-arpa", &print_arpa, "print arpa. zone name of address" }, { "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" }, { "resolve-stub", &resolve_query, "resolve as stub resolver" }, { "resolve-recurse", &resolve_query, "resolve as recursive resolver" }, { "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" }, { "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" }, /* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */ { "dump-trace", &dump_trace, "dump the contents of a trace file" }, { "echo", &echo_port, "server echo mode, for nmap fuzzing" }, { "isection", &isection, "parse section string" }, { "iclass", &iclass, "parse class string" }, { "itype", &itype, "parse type string" }, { "iopcode", &iopcode, "parse opcode string" }, { "ircode", &ircode, "parse rcode string" }, { "sizes", &sizes, "print data structure sizes" }, }; static void print_usage(const char *progname, FILE *fp) { static const char *usage = " [OPTIONS] COMMAND [ARGS]\n" " -c PATH Path to resolv.conf\n" " -n PATH Path to nsswitch.conf\n" " -l PATH Path to local hosts\n" " -z PATH Path to zone cache\n" " -q QNAME Query name\n" " -t QTYPE Query type\n" " -s HOW Sort records\n" " -S ADDR Address of SOCKS server to use\n" " -P PORT Port of SOCKS server to use\n" " -A USER:PASSWORD Credentials for the SOCKS server\n" " -f PATH Path to trace file\n" " -v Be more verbose (-vv show packets; -vvv hexdump packets)\n" " -V Print version info\n" " -h Print this usage message\n" "\n"; unsigned i, n, m; fputs(progname, fp); fputs(usage, fp); for (i = 0, m = 0; i < lengthof(cmds); i++) { if (strlen(cmds[i].cmd) > m) m = strlen(cmds[i].cmd); } for (i = 0; i < lengthof(cmds); i++) { fprintf(fp, " %s ", cmds[i].cmd); for (n = strlen(cmds[i].cmd); n < m; n++) putc(' ', fp); fputs(cmds[i].help, fp); putc('\n', fp); } fputs("\nReport bugs to William Ahern \n", fp); } /* print_usage() */ static void print_version(const char *progname, FILE *fp) { fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel()); fprintf(fp, "vendor %s\n", dns_vendor()); fprintf(fp, "release %.8X\n", dns_v_rel()); fprintf(fp, "abi %.8X\n", dns_v_abi()); fprintf(fp, "api %.8X\n", dns_v_api()); } /* print_version() */ static void main_exit(void) { dns_trace_close(MAIN.memo.trace); MAIN.memo.trace = NULL; dns_hosts_close(MAIN.memo.hosts); MAIN.memo.hosts = NULL; dns_resconf_close(MAIN.memo.resconf); MAIN.memo.resconf = NULL; } /* main_exit() */ int main(int argc, char **argv) { extern int optind; extern char *optarg; const char *progname = argv[0]; unsigned i; int ch; atexit(&main_exit); while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) { switch (ch) { case 'c': assert(MAIN.resconf.count < lengthof(MAIN.resconf.path)); MAIN.resconf.path[MAIN.resconf.count++] = optarg; break; case 'n': assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path)); MAIN.nssconf.path[MAIN.nssconf.count++] = optarg; break; case 'l': assert(MAIN.hosts.count < lengthof(MAIN.hosts.path)); MAIN.hosts.path[MAIN.hosts.count++] = optarg; break; case 'z': assert(MAIN.cache.count < lengthof(MAIN.cache.path)); MAIN.cache.path[MAIN.cache.count++] = optarg; break; case 'q': MAIN.qname = optarg; break; case 't': for (i = 0; i < lengthof(dns_rrtypes); i++) { if (0 == strcasecmp(dns_rrtypes[i].name, optarg)) { MAIN.qtype = dns_rrtypes[i].type; break; } } if (MAIN.qtype) break; for (i = 0; dns_isdigit(optarg[i]); i++) { MAIN.qtype *= 10; MAIN.qtype += optarg[i] - '0'; } if (!MAIN.qtype) panic("%s: invalid query type", optarg); break; case 's': if (0 == strcasecmp(optarg, "packet")) MAIN.sort = &dns_rr_i_packet; else if (0 == strcasecmp(optarg, "shuffle")) MAIN.sort = &dns_rr_i_shuffle; else if (0 == strcasecmp(optarg, "order")) MAIN.sort = &dns_rr_i_order; else panic("%s: invalid sort method", optarg); break; case 'S': { dns_error_t error; struct dns_resolv_conf *conf = resconf(); conf->options.tcp = DNS_RESCONF_TCP_SOCKS; MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET; if ((error = dns_pton(MAIN.socks_host.ss_family, optarg, dns_sa_addr(MAIN.socks_host.ss_family, &MAIN.socks_host, NULL)))) panic("%s: %s", optarg, dns_strerror(error)); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080); break; } case 'P': if (! MAIN.socks_host.ss_family) panic("-P without prior -S"); *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg)); break; case 'A': { char *password; if (! MAIN.socks_host.ss_family) panic("-A without prior -S"); if (! (password = strchr(optarg, ':'))) panic("Usage: -A USER:PASSWORD"); *password = 0; password += 1; MAIN.socks_user = optarg; MAIN.socks_password = password; break; } case 'f': MAIN.trace = optarg; break; case 'v': dns_debug = ++MAIN.verbose; break; case 'V': print_version(progname, stdout); return 0; case 'h': print_usage(progname, stdout); return 0; default: print_usage(progname, stderr); return EXIT_FAILURE; } /* switch() */ } /* while() */ argc -= optind; argv += optind; for (i = 0; i < lengthof(cmds) && argv[0]; i++) { if (0 == strcmp(cmds[i].cmd, argv[0])) return cmds[i].run(argc, argv); } print_usage(progname, stderr); return EXIT_FAILURE; } /* main() */ #endif /* DNS_MAIN */ /* * pop file-scoped compiler annotations */ #if __clang__ #pragma clang diagnostic pop #elif DNS_GNUC_PREREQ(4,6,0) #pragma GCC diagnostic pop #endif diff --git a/doc/DETAILS b/doc/DETAILS index e7567f7e3..eb6d7dd4b 100644 --- a/doc/DETAILS +++ b/doc/DETAILS @@ -1,1569 +1,1580 @@ # doc/DETAILS -*- org -*- #+TITLE: GnuPG Details # Globally disable superscripts and subscripts: #+OPTIONS: ^:{} # # Note: This file uses org-mode; it should be easy to read as plain # text but be aware of some markup peculiarities: Verbatim code is # enclosed in #+begin-example, #+end-example blocks or marked by a # colon as the first non-white-space character, words bracketed with # equal signs indicate a monospace font, and the usual /italics/, # *bold*, and _underline_ conventions are recognized. This is the DETAILS file for GnuPG which specifies some internals and parts of the external API for GPG and GPGSM. * Format of the colon listings The format is a based on colon separated record, each recods starts with a tag string and extends to the end of the line. Here is an example: #+begin_example $ gpg --with-colons --list-keys \ --with-fingerprint --with-fingerprint wk@gnupg.org pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC: fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013: uid:f::::::::Werner Koch : uid:f::::::::Werner Koch : sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e: fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1: sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc: fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4: #+end_example Note that new version of GnuPG or the use of certain options may add new fields to the output. Parsers should not assume a limit on the number of fields per line. Some fields are not yet used or only used with certain record types; parsers should ignore fields they are not aware of. New versions of GnuPG or the use of certain options may add new types of records as well. Parsers should ignore any record whose type they do not recognize for forward-compatibility. The double =--with-fingerprint= prints the fingerprint for the subkeys too. Old versions of gpg used a slightly different format and required the use of the option =--fixed-list-mode= to conform to the format described here. ** Description of the fields *** Field 1 - Type of record - pub :: Public key - crt :: X.509 certificate - crs :: X.509 certificate and private key available - sub :: Subkey (secondary key) - sec :: Secret key - ssb :: Secret subkey (secondary key) - uid :: User id - uat :: User attribute (same as user id except for field 10). - sig :: Signature - rev :: Revocation signature + - rvs :: Recocation signature (standalone) [since 2.2.9] - fpr :: Fingerprint (fingerprint is in field 10) - pkd :: Public key data [*] - grp :: Keygrip - rvk :: Revocation key - tfs :: TOFU statistics [*] - tru :: Trust database information [*] - spk :: Signature subpacket [*] - cfg :: Configuration data [*] Records marked with an asterisk are described at [[*Special%20field%20formats][*Special fields]]. *** Field 2 - Validity This is a letter describing the computed validity of a key. Currently this is a single letter, but be prepared that additional information may follow in some future versions. Note that GnuPG < 2.1 does not set this field for secret key listings. - o :: Unknown (this key is new to the system) - i :: The key is invalid (e.g. due to a missing self-signature) - d :: The key has been disabled (deprecated - use the 'D' in field 12 instead) - r :: The key has been revoked - e :: The key has expired - - :: Unknown validity (i.e. no value assigned) - q :: Undefined validity. '-' and 'q' may safely be treated as the same value for most purposes - n :: The key is not valid - m :: The key is marginal valid. - f :: The key is fully valid - u :: The key is ultimately valid. This often means that the secret key is available, but any key may be marked as ultimately valid. - w :: The key has a well known private part. - s :: The key has special validity. This means that it might be self-signed and expected to be used in the STEED system. If the validity information is given for a UID or UAT record, it describes the validity calculated based on this user ID. If given for a key record it describes the validity taken from the best rated user ID. For X.509 certificates a 'u' is used for a trusted root certificate (i.e. for the trust anchor) and an 'f' for all other valid certificates. In "sig" records, this field may have one of these values as first character: - ! :: Signature is good. - - :: Signature is bad. - ? :: No public key to verify signature or public key is not usable. - % :: Other error verifying a signature More values may be added later. The field may also be empty if gpg has been invoked in a non-checking mode (--list-sigs) or in a fast checking mode. Since 2.2.7 '?' will also be printed by the command --list-sigs if the key is not in the local keyring. *** Field 3 - Key length The length of key in bits. *** Field 4 - Public key algorithm The values here are those from the OpenPGP specs or if they are greather than 255 the algorithm ids as used by Libgcrypt. *** 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. *** Field 6 - Creation date The creation date of the key is given in UTC. For UID and UAT records, this is used for the self-signature date. Note that the date is usually printed in seconds since epoch, however, we are migrating to an ISO 8601 format (e.g. "19660205T091500"). This is currently only relevant for X.509. A simple way to detect the new format is to scan for the 'T'. Note that old versions of gpg without using the =--fixed-list-mode= option used a "yyyy-mm-tt" format. *** Field 7 - Expiration date Key or UID/UAT expiration date or empty if it does not expire. *** Field 8 - Certificate S/N, UID hash, trust signature info Used for serial number in crt records. For UID and UAT records, this is a hash of the user ID contents used to represent that exact user ID. For trust signatures, this is the trust depth separated by the trust value by a space. *** Field 9 - Ownertrust 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. *** 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 subpacket count here, a space, and then the total attribute subpacket size. In gpgsm the issuer name comes here. A FPR record stores the fingerprint here. The fingerprint of a revocation key is stored here. *** Field 11 - Signature class Signature class as per RFC-4880. This is a 2 digit hexnumber followed by either the letter 'x' for an exportable signature or the letter 'l' for a local-only signature. The class byte of an revocation key is also given here, 'x' and 'l' is used the same way. This field if not used for X.509. + "rev" and "rvs" may be followed by a comma and a 2 digit hexnumber + with the revocation reason. + *** Field 12 - Key capabilities The defined capabilities are: - e :: Encrypt - s :: Sign - c :: Certify - a :: Authentication - ? :: Unknown capability A key may have any combination of them in any order. In addition to these letters, the primary key has uppercase versions of the letters to denote the _usable_ capabilities of the entire key, and a potential letter 'D' to indicate a disabled key. *** Field 13 - Issuer certificate fingerprint or other info Used in FPR records for S/MIME keys to store the fingerprint of the issuer certificate. This is useful to build the certificate path based on certificates stored in the local key database it is only filled if the issuer certificate is available. The root has been reached if this is the same string as the fingerprint. The advantage of using this value is that it is guaranteed to have been built by the same lookup algorithm as gpgsm uses. For "uid" records this field lists the preferences in the same way gpg's --edit-key menu does. - For "sig" records, this is the fingerprint of the key that issued - the signature. Note that this may only be filled if the signature - verified correctly. Note also that for various technical reasons, - this fingerprint is only available if --no-sig-cache is used. - Since 2.2.7 this field will also be set if the key is missing but - the signature carries an issuer fingerprint as meta data. + For "sig", "rev" and "rvs" records, this is the fingerprint of the + key that issued the signature. Note that this may only be filled + if the signature verified correctly. Note also that for various + technical reasons, this fingerprint is only available if + --no-sig-cache is used. Since 2.2.7 this field will also be set + if the key is missing but the signature carries an issuer + fingerprint as meta data. *** Field 14 - Flag field Flag field used in the --edit menu output *** Field 15 - S/N of a token Used in sec/ssb to print the serial number of a token (internal protect mode 1002) or a '#' if that key is a simple stub (internal protect mode 1001). If the option --with-secret is used and a secret key is available for the public key, a '+' indicates this. *** Field 16 - Hash algorithm For sig records, this is the used hash algorithm. For example: 2 = SHA-1, 8 = SHA-256. *** Field 17 - Curve name For pub, sub, sec, and ssb records this field is used for the ECC curve name. *** Field 18 - Compliance flags Space separated list of asserted compliance modes and screening result for this key. Valid values are: - 8 :: The key is compliant with RFC4880bis - 23 :: The key is compliant with compliance mode "de-vs". - 6001 :: Screening hit on the ROCA vulnerability. *** Field 19 - Last update The timestamp of the last update of a key or user ID. The update time of a key is defined a lookup of the key via its unique identifier (fingerprint); the field is empty if not known. The update time of a user ID is defined by a lookup of the key using a trusted mapping from mail address to key. *** Field 20 - Origin 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. +*** 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. + ** Special fields *** PKD - Public key data If field 1 has the tag "pkd", a listing looks like this: #+begin_example pkd:0:1024:B665B1435F4C2 .... FF26ABB: ! ! !-- the value ! !------ for information number of bits in the value !--------- index (eg. DSA goes from 0 to 3: p,q,g,y) #+end_example *** TFS - TOFU statistics This field may follows a UID record to convey information about the TOFU database. The information is similar to a TOFU_STATS status line. - Field 2 :: tfs record version (must be 1) - Field 3 :: validity - A number with validity code. - Field 4 :: signcount - The number of signatures seen. - Field 5 :: encrcount - The number of encryptions done. - Field 6 :: policy - A string with the policy - Field 7 :: signture-first-seen - a timestamp or 0 if not known. - Field 8 :: signature-most-recent-seen - a timestamp or 0 if not known. - Field 9 :: encryption-first-done - a timestamp or 0 if not known. - Field 10 :: encryption-most-recent-done - a timestamp or 0 if not known. *** TRU - Trust database information Example for a "tru" trust base record: #+begin_example tru:o:0:1166697654:1:3:1:5 #+end_example - Field 2 :: Reason for staleness of trust. If this field is empty, then the trustdb is not stale. This field may have multiple flags in it: - o :: Trustdb is old - t :: Trustdb was built with a different trust model than the one we are using now. - Field 3 :: Trust model - 0 :: Classic trust model, as used in PGP 2.x. - 1 :: PGP trust model, as used in PGP 6 and later. This is the same as the classic trust model, except for the addition of trust signatures. GnuPG before version 1.4 used the classic trust model by default. GnuPG 1.4 and later uses the PGP trust model by default. - Field 4 :: Date trustdb was created in seconds since Epoch. - Field 5 :: Date trustdb will expire in seconds since Epoch. - Field 6 :: Number of marginally trusted users to introduce a new key signer (gpg's option --marginals-needed). - Field 7 :: Number of completely trusted users to introduce a new key signer. (gpg's option --completes-needed) - Field 8 :: Maximum depth of a certification chain. (gpg's option --max-cert-depth) *** SPK - Signature subpacket records - Field 2 :: Subpacket number as per RFC-4880 and later. - Field 3 :: Flags in hex. Currently the only two bits assigned are 1, to indicate that the subpacket came from the hashed part of the signature, and 2, to indicate the subpacket was marked critical. - Field 4 :: Length of the subpacket. Note that this is the length of the subpacket, and not the length of field 5 below. Due to the need for %-encoding, the length of field 5 may be up to 3x this value. - Field 5 :: The subpacket data. Printable ASCII is shown as ASCII, but other values are rendered as %XX where XX is the hex value for the byte. *** CFG - Configuration data --list-config outputs information about the GnuPG configuration for the benefit of frontends or other programs that call GnuPG. There are several list-config items, all colon delimited like the rest of the --with-colons output. The first field is always "cfg" to indicate configuration information. The second field is one of (with examples): - version :: The third field contains the version of GnuPG. : cfg:version:1.3.5 - pubkey :: The third field contains the public key algorithms this version of GnuPG supports, separated by semicolons. The algorithm numbers are as specified in RFC-4880. Note that in contrast to the --status-fd interface these are _not_ the Libgcrypt identifiers. Using =pubkeyname= prints names instead of numbers. : cfg:pubkey:1;2;3;16;17 - cipher :: The third field contains the symmetric ciphers this version of GnuPG supports, separated by semicolons. The cipher numbers are as specified in RFC-4880. Using =ciphername= prints names instead of numbers. : cfg:cipher:2;3;4;7;8;9;10 - digest :: The third field contains the digest (hash) algorithms this version of GnuPG supports, separated by semicolons. The digest numbers are as specified in RFC-4880. Using =digestname= prints names instead of numbers. : cfg:digest:1;2;3;8;9;10 - compress :: The third field contains the compression algorithms this version of GnuPG supports, separated by semicolons. The algorithm numbers are as specified in RFC-4880. : cfg:compress:0;1;2;3 - group :: The third field contains the name of the group, and the fourth field contains the values that the group expands to, separated by semicolons. For example, a group of: : group mynames = paige 0x12345678 joe patti would result in: : cfg:group:mynames:patti;joe;0x12345678;paige - curve :: The third field contains the curve names this version of GnuPG supports, separated by semicolons. Using =curveoid= prints OIDs instead of numbers. : cfg:curve:ed25519;nistp256;nistp384;nistp521 * Format of the --status-fd output Every line is prefixed with "[GNUPG:] ", followed by a keyword with the type of the status line and some arguments depending on the type (maybe none); an application should always be willing to ignore unknown keywords that may be emitted by future versions of GnuPG. Also, new versions of GnuPG may add arguments to existing keywords. Any additional arguments should be ignored for forward-compatibility. ** General status codes *** NEWSIG [] Is issued right before a signature verification starts. This is useful to define a context for parsing ERROR status messages. If SIGNERS_UID is given and is not "-" this is the percent-escaped value of the OpenPGP Signer's User ID signature sub-packet. *** GOODSIG The signature with the keyid is good. For each signature only one of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or ERRSIG will be emitted. In the past they were used as a marker for a new signature; new code should use the NEWSIG status instead. The username is the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be used instead of the long keyid if it is available. This is the case with CMS and might eventually also be available for OpenPGP. *** EXPSIG The signature with the keyid is good, but the signature is expired. The username is the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be used instead of the long keyid if it is available. This is the case with CMS and might eventually also be available for OpenPGP. *** EXPKEYSIG The signature with the keyid is good, but the signature was made by an expired key. The username is the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be used instead of the long keyid if it is available. This is the case with CMS and might eventually also be available for OpenPGP. *** REVKEYSIG The signature with the keyid is good, but the signature was made by a revoked key. The username is the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be used instead of the long keyid if it is available. This is the case with CMS and might eventually also beñ available for OpenPGP. *** BADSIG The signature with the keyid has not been verified okay. The username is the primary one encoded in UTF-8 and %XX escaped. The fingerprint may be used instead of the long keyid if it is available. This is the case with CMS and might eventually also be available for OpenPGP. *** ERRSIG