diff --git a/g10/gpg.c b/g10/gpg.c index 6e54aa763..b766e318f 100644 --- a/g10/gpg.c +++ b/g10/gpg.c @@ -1,5826 +1,5826 @@ /* gpg.c - The GnuPG OpenPGP tool * Copyright (C) 1998-2020 Free Software Foundation, Inc. * Copyright (C) 1997-2019 Werner Koch * Copyright (C) 2015-2022 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_STAT #include /* for stat() */ #endif #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #endif #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include #include "../common/iobuf.h" #include "../common/util.h" #include "packet.h" #include "../common/membuf.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/status.h" #include "keyserver-internal.h" #include "exec.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "call-dirmngr.h" #include "tofu.h" #include "objcache.h" #include "../common/init.h" #include "../common/mbox-util.h" #include "../common/zb32.h" #include "../common/shareddefs.h" #include "../common/compliance.h" #include "../common/comopt.h" #include "../kbx/keybox.h" #if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__) #define MY_O_BINARY O_BINARY #ifndef S_IRGRP # define S_IRGRP 0 # define S_IWGRP 0 #endif #else #define MY_O_BINARY 0 #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', oRecipientFile = 'f', oHiddenRecipientFile = 'F', oInteractive = 'i', aListKeys = 'k', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', oHiddenRecipient = 'R', aSign = 's', oTextmodeShort= 't', oLocalUser = 'u', oVerbose = 'v', oCompress = 'z', oSetNotation = 'N', aListSecretKeys = 'K', oBatch = 500, oMaxOutput, oInputSizeHint, oChunkSize, oSigNotation, oCertNotation, oShowNotation, oNoShowNotation, oKnownNotation, aEncrFiles, aEncrSym, aDecryptFiles, aClearsign, aStore, aQuickKeygen, aFullKeygen, aKeygen, aSignEncr, aSignEncrSym, aSignSym, aSignKey, aLSignKey, aQuickSignKey, aQuickLSignKey, aQuickRevSig, aQuickAddUid, aQuickAddKey, aQuickAddADSK, aQuickRevUid, aQuickSetExpire, aQuickSetPrimaryUid, aQuickUpdatePref, aListConfig, aListGcryptConfig, aGPGConfList, aGPGConfTest, aListPackets, aEditKey, aDeleteKeys, aDeleteSecretKeys, aDeleteSecretAndPublicKeys, aImport, aFastImport, aVerify, aVerifyFiles, aListSigs, aSendKeys, aRecvKeys, aLocateKeys, aLocateExtKeys, aSearchKeys, aRefreshKeys, aFetchKeys, aShowKeys, aExport, aExportSecret, aExportSecretSub, aExportSshKey, aExportSecretSshKey, aCheckKeys, aGenRevoke, aDesigRevoke, aPrimegen, aPrintMD, aPrintMDs, aCheckTrustDB, aUpdateTrustDB, aFixTrustDB, aListTrustDB, aListTrustPath, aExportOwnerTrust, aImportOwnerTrust, aDeArmor, aEnArmor, aGenRandom, aRebuildKeydbCaches, aCardStatus, aCardEdit, aChangePIN, aPasswd, aServer, aTOFUPolicy, oMimemode, oTextmode, oNoTextmode, oExpert, oNoExpert, oDefSigExpire, oAskSigExpire, oNoAskSigExpire, oDefCertExpire, oAskCertExpire, oNoAskCertExpire, oDefCertLevel, oMinCertLevel, oAskCertLevel, oNoAskCertLevel, oFingerprint, oWithFingerprint, oWithSubkeyFingerprint, oWithICAOSpelling, oWithKeygrip, oWithKeyScreening, oWithSecret, oWithWKDHash, oWithColons, oWithKeyData, oWithKeyOrigin, oWithTofuInfo, oWithSigList, oWithSigCheck, oAnswerYes, oAnswerNo, oKeyring, oPrimaryKeyring, oSecretKeyring, oShowKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oTrySecretKey, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugIOLBF, oDebugSetIobufSize, oDebugAllowLargeChunks, oDebugIgnoreExpiration, oStatusFD, oStatusFile, oAttributeFD, oAttributeFile, oEmitVersion, oNoEmitVersion, oCompletesNeeded, oMarginalsNeeded, oMaxCertDepth, oLoadExtension, oCompliance, oGnuPG, oRFC2440, oRFC4880, oOpenPGP, oPGP7, oPGP8, oDE_VS, oMinRSALength, oRFC2440Text, oNoRFC2440Text, oCipherAlgo, oDigestAlgo, oCertDigestAlgo, oCompressAlgo, oCompressLevel, oBZ2CompressLevel, oBZ2DecompressLowmem, oPassphrase, oPassphraseFD, oPassphraseFile, oPassphraseRepeat, oPinentryMode, oCommandFD, oCommandFile, oQuickRandom, oNoVerbose, oTrustDBName, oNoSecmemWarn, oRequireSecmem, oNoRequireSecmem, oNoPermissionWarn, oNoArmor, oNoDefKeyring, oNoKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oSkipVerify, oSkipHiddenRecipients, oNoSkipHiddenRecipients, oAlwaysTrust, oTrustModel, oForceOwnertrust, oNoAutoTrustNewKey, oSetFilename, oForYourEyesOnly, oNoForYourEyesOnly, oSetPolicyURL, oSigPolicyURL, oCertPolicyURL, oShowPolicyURL, oNoShowPolicyURL, oSigKeyserverURL, oUseEmbeddedFilename, oNoUseEmbeddedFilename, oComment, oDefaultComment, oNoComments, oThrowKeyids, oNoThrowKeyids, oShowPhotos, oNoShowPhotos, oPhotoViewer, oForceAEAD, oS2KMode, oS2KDigest, oS2KCipher, oS2KCount, oDisplayCharset, oNotDashEscaped, oEscapeFrom, oNoEscapeFrom, oLockOnce, oLockMultiple, oLockNever, oKeyServer, oKeyServerOptions, oImportOptions, oImportFilter, oExportOptions, oExportFilter, oListOptions, oListFilter, oVerifyOptions, oTempDir, oExecPath, oEncryptTo, oHiddenEncryptTo, oNoEncryptTo, oEncryptToDefaultKey, oLoggerFD, oLoggerFile, oLogTime, oUtf8Strings, oNoUtf8Strings, oDisableCipherAlgo, oDisablePubkeyAlgo, oAllowNonSelfsignedUID, oNoAllowNonSelfsignedUID, oAllowFreeformUID, oNoAllowFreeformUID, oAllowSecretKeyImport, oAllowOldCipherAlgos, oEnableSpecialFilenames, oNoLiteral, oSetFilesize, oHonorHttpProxy, oFastListMode, oListOnly, oIgnoreTimeConflict, oIgnoreValidFrom, oIgnoreCrcError, oIgnoreMDCError, oShowSessionKey, oOverrideSessionKey, oOverrideSessionKeyFD, oNoRandomSeedFile, oAutoKeyRetrieve, oNoAutoKeyRetrieve, oAutoKeyImport, oNoAutoKeyImport, oUseAgent, oNoUseAgent, oGpgAgentInfo, oUseKeyboxd, oMergeOnly, oTryAllSecrets, oTrustedKey, oNoExpensiveTrustChecks, oFixedListMode, oLegacyListMode, oNoSigCache, oAutoCheckTrustDB, oNoAutoCheckTrustDB, oPreservePermissions, oDefaultPreferenceList, oDefaultKeyserverURL, oPersonalCipherPreferences, oPersonalDigestPreferences, oPersonalCompressPreferences, oAgentProgram, oKeyboxdProgram, oDirmngrProgram, oDisableDirmngr, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oGroup, oUnGroup, oNoGroups, oStrict, oNoStrict, oMangleDosFilenames, oNoMangleDosFilenames, oEnableProgressFilter, oMultifile, oKeyidFormat, oExitOnStatusWriteError, oLimitCardInsertTries, oReaderPort, octapiDriver, opcscDriver, oDisableCCID, oRequireCrossCert, oNoRequireCrossCert, oAutoKeyLocate, oNoAutoKeyLocate, oEnableLargeRSA, oDisableLargeRSA, oEnableDSA2, oDisableDSA2, oAllowWeakDigestAlgos, oAllowWeakKeySignatures, oFakedSystemTime, oNoAutostart, oPrintDANERecords, oTOFUDefaultPolicy, oTOFUDBFormat, oDefaultNewKeyAlgo, oWeakDigest, oUnwrap, oOnlySignTextIDs, oDisableSignerUID, oSender, oKeyOrigin, oRequestOrigin, oNoSymkeyCache, oUseOnlyOpenPGPCard, oFullTimestrings, oIncludeKeyBlock, oNoIncludeKeyBlock, oChUid, oForceSignKey, oForbidGenKey, oRequireCompliance, oCompatibilityFlags, oAddDesigRevoker, oAssertSigner, oKbxBufferSize, oNoop }; static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aSign, "sign", N_("make a signature")), ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")), ARGPARSE_c (aClearsign, "clearsign", "@"), ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), ARGPARSE_c (aEncrFiles, "encrypt-files", "@"), ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")), ARGPARSE_c (aStore, "store", "@"), ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"), ARGPARSE_c (aVerify, "verify" , N_("verify a signature")), ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ), ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), ARGPARSE_c (aListKeys, "list-public-keys", "@" ), ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")), ARGPARSE_c (aListSigs, "list-sigs", "@"), ARGPARSE_c (aCheckKeys, "check-signatures", N_("list and check key signatures")), ARGPARSE_c (aCheckKeys, "check-sigs", "@"), ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")), ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")), ARGPARSE_c (aKeygen, "gen-key", "@"), ARGPARSE_c (aQuickKeygen, "quick-generate-key" , N_("quickly generate a new key pair")), ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"), ARGPARSE_c (aQuickAddUid, "quick-add-uid", N_("quickly add a new user-id")), ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"), ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"), ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"), ARGPARSE_c (aQuickAddADSK, "quick-add-adsk", "@"), ARGPARSE_c (aQuickRevUid, "quick-revoke-uid", N_("quickly revoke a user-id")), ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"), ARGPARSE_c (aQuickSetExpire, "quick-set-expire", N_("quickly set a new expiration date")), ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"), ARGPARSE_c (aQuickUpdatePref, "quick-update-pref", "@"), ARGPARSE_c (aFullKeygen, "full-generate-key" , N_("full featured key pair generation")), ARGPARSE_c (aFullKeygen, "full-gen-key", "@"), ARGPARSE_c (aGenRevoke, "generate-revocation", N_("generate a revocation certificate")), ARGPARSE_c (aGenRevoke, "gen-revoke", "@"), ARGPARSE_c (aDeleteKeys,"delete-keys", N_("remove keys from the public keyring")), ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys", N_("remove keys from the secret keyring")), ARGPARSE_c (aQuickSignKey, "quick-sign-key" , N_("quickly sign a key")), ARGPARSE_c (aQuickLSignKey, "quick-lsign-key", N_("quickly sign a key locally")), ARGPARSE_c (aQuickRevSig, "quick-revoke-sig" , N_("quickly revoke a key signature")), ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")), ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")), ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")), ARGPARSE_c (aEditKey, "key-edit" ,"@"), ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), ARGPARSE_c (aPasswd, "passwd", "@"), ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"), ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ), ARGPARSE_c (aExport, "export" , N_("export keys") ), ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ), ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ), ARGPARSE_c (aRecvKeys, "recv-keys" , "@"), ARGPARSE_c (aSearchKeys, "search-keys" , N_("search for keys on a keyserver") ), ARGPARSE_c (aRefreshKeys, "refresh-keys", N_("update all keys from a keyserver")), ARGPARSE_c (aLocateKeys, "locate-keys", "@"), ARGPARSE_c (aLocateExtKeys, "locate-external-keys", "@"), ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ), ARGPARSE_c (aShowKeys, "show-keys" , "@" ), ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ), ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ), ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ), ARGPARSE_c (aExportSecretSshKey, "export-secret-ssh-key", "@" ), ARGPARSE_c (aImport, "import", N_("import/merge keys")), ARGPARSE_c (aFastImport, "fast-import", "@"), #ifdef ENABLE_CARD_SUPPORT ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")), ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")), ARGPARSE_c (aCardEdit, "card-edit", "@"), ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")), #endif ARGPARSE_c (aListConfig, "list-config", "@"), ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ), ARGPARSE_c (aListPackets, "list-packets","@"), #ifndef NO_TRUST_MODELS ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"), ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"), ARGPARSE_c (aUpdateTrustDB,"update-trustdb", N_("update the trust database")), ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"), ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"), ARGPARSE_c (aListTrustDB, "list-trustdb", "@"), #endif ARGPARSE_c (aDeArmor, "dearmor", "@"), ARGPARSE_c (aDeArmor, "dearmour", "@"), ARGPARSE_c (aEnArmor, "enarmor", "@"), ARGPARSE_c (aEnArmor, "enarmour", "@"), ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")), ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */ ARGPARSE_c (aPrimegen, "gen-prime", "@" ), ARGPARSE_c (aGenRandom,"gen-random", "@" ), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aTOFUPolicy, "tofu-policy", N_("|VALUE|set the TOFU policy for a key")), /* Not yet used: ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */ ARGPARSE_c (aDeleteSecretAndPublicKeys, "delete-secret-and-public-keys", "@"), ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"), ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */ ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */ ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */ ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */ ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoTTY, "no-tty", "@"), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"), ARGPARSE_s_u (oDebugSetIobufSize, "debug-set-iobuf-size", "@"), ARGPARSE_s_u (oDebugAllowLargeChunks, "debug-allow-large-chunks", "@"), ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"), ARGPARSE_s_s (oDisplayCharset, "charset", "@"), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), - ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_s (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_s (oLoggerFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */ ARGPARSE_s_n (oLogTime, "log-time", "@"), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_s (oDefaultKey, "default-key", N_("|NAME|use NAME as default secret key")), ARGPARSE_s_s (oEncryptTo, "encrypt-to", N_("|NAME|encrypt to user ID NAME as well")), ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"), ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"), ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"), ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"), ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), ARGPARSE_s_s (oGroup, "group", N_("|SPEC|set up email aliases")), ARGPARSE_s_s (oUnGroup, "ungroup", "@"), ARGPARSE_s_n (oNoGroups, "no-groups", "@"), ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_s_n (oGnuPG, "gnupg", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"), ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"), ARGPARSE_s_n (oRFC2440, "rfc2440", "@"), ARGPARSE_s_n (oRFC4880, "rfc4880", "@"), ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")), ARGPARSE_s_n (oPGP7, "pgp6", "@"), ARGPARSE_s_n (oPGP7, "pgp7", "@"), ARGPARSE_s_n (oPGP8, "pgp8", "@"), ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"), ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), #ifndef NO_TRUST_MODELS ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"), #endif ARGPARSE_s_s (oTrustModel, "trust-model", "@"), ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"), ARGPARSE_s_s (oKnownNotation, "known-notation", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"), ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"), ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"), ARGPARSE_s_s (oTempDir, "temp-directory", "@"), ARGPARSE_s_s (oExecPath, "exec-path", "@"), ARGPARSE_s_n (oExpert, "expert", "@"), ARGPARSE_s_n (oNoExpert, "no-expert", "@"), ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"), ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"), ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")), ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"), ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"), ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"), ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"), ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"), ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"), ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"), ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"), ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"), ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"), ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"), ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"), ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"), ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"), ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"), ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-preferences", "@"), ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"), ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"), ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"), ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"), ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"), ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"), ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"), ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */ ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"), ARGPARSE_s_n (oLockOnce, "lock-once", "@"), ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"), ARGPARSE_s_n (oLockNever, "lock-never", "@"), ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"), ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */ ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"), ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"), ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"), ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ), #ifndef NO_TRUST_MODELS ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"), ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"), ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"), ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"), ARGPARSE_s_n (oNoAutoTrustNewKey, "no-auto-trust-new-key", "@"), #endif ARGPARSE_s_s (oAddDesigRevoker, "add-desig-revoker", "@"), ARGPARSE_s_s (oAssertSigner, "assert-signer", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), ARGPARSE_s_n (oMultifile, "multifile", "@"), ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"), ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"), ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"), ARGPARSE_s_n (oNoLiteral, "no-literal", "@"), ARGPARSE_s_s (oSetNotation, "set-notation", "@"), ARGPARSE_s_s (oSigNotation, "sig-notation", "@"), ARGPARSE_s_s (oCertNotation, "cert-notation", "@"), ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"), ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"), ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"), ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"), ARGPARSE_header ("Output", N_("Options controlling the output")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_n (oArmor, "armour", "@"), ARGPARSE_s_n (oNoArmor, "no-armor", "@"), ARGPARSE_s_n (oNoArmor, "no-armour", "@"), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_p_u (oMaxOutput, "max-output", "@"), ARGPARSE_s_s (oComment, "comment", "@"), ARGPARSE_s_n (oDefaultComment, "default-comment", "@"), ARGPARSE_s_n (oNoComments, "no-comments", "@"), ARGPARSE_s_n (oEmitVersion, "emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"), ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */ ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"), ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"), ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"), ARGPARSE_s_n (oMimemode, "mimemode", "@"), ARGPARSE_s_n (oTextmodeShort, NULL, "@"), ARGPARSE_s_n (oTextmode, "textmode", N_("use canonical text mode")), ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"), ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"), ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"), ARGPARSE_s_n (oShowNotation, "show-notation", "@"), ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"), ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"), ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"), ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"), ARGPARSE_s_n (oUnwrap, "unwrap", "@"), ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"), ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"), ARGPARSE_s_i (oChunkSize, "chunk-size", "@"), ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"), ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), ARGPARSE_s_n (oListOnly, "list-only", "@"), ARGPARSE_s_i (oCompress, NULL, N_("|N|set compress level to N (0 disables)")), ARGPARSE_s_i (oCompressLevel, "compress-level", "@"), ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"), ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"), ARGPARSE_header ("ImportExport", N_("Options controlling key import and export")), ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address")), ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"), ARGPARSE_s_n (oAutoKeyImport, "auto-key-import", N_("import missing key from a signature")), ARGPARSE_s_n (oNoAutoKeyImport, "no-auto-key-import", "@"), ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"), ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"), ARGPARSE_s_n (oIncludeKeyBlock, "include-key-block", N_("include the public key in signatures")), ARGPARSE_s_n (oNoIncludeKeyBlock, "no-include-key-block", "@"), ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", N_("disable all access to the dirmngr")), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), /* Deprecated. */ ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"), ARGPARSE_s_s (oKeyOrigin, "key-origin", "@"), ARGPARSE_s_s (oImportOptions, "import-options", "@"), ARGPARSE_s_s (oImportFilter, "import-filter", "@"), ARGPARSE_s_s (oExportOptions, "export-options", "@"), ARGPARSE_s_s (oExportFilter, "export-filter", "@"), ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ), ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"), ARGPARSE_header ("Keylist", N_("Options controlling key listings")), ARGPARSE_s_s (oListOptions, "list-options", "@"), ARGPARSE_s_s (oListFilter, "list-filter", "@"), ARGPARSE_s_n (oFullTimestrings, "full-timestrings", "@"), ARGPARSE_s_n (oShowPhotos, "show-photos", "@"), ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"), ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"), ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"), ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"), ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"), ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"), ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"), ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"), ARGPARSE_s_n (oWithKeyOrigin, "with-key-origin", "@"), ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"), ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"), ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"), ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"), ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"), ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"), ARGPARSE_header (NULL, N_("Options to specify keys")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"), ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"), ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"), ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */ ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"), ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"), ARGPARSE_s_s (oLocalUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"), ARGPARSE_s_s (oSender, "sender", "@"), ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"), ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"), ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"), ARGPARSE_s_s (oKeyring, "keyring", "@"), ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"), ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"), ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"), ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"), ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"), - ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"), + ARGPARSE_s_s (oOverrideSessionKeyFD, "override-session-key-fd", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"), ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"), ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"), ARGPARSE_s_i (oS2KCount, "s2k-count", "@"), ARGPARSE_s_n (oForceAEAD, "force-ocb", "@"), ARGPARSE_s_n (oForceAEAD, "force-aead", "@"), /*(old name)*/ ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"), ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"), ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"), /* Options to override new security defaults. */ ARGPARSE_s_n (oAllowWeakKeySignatures, "allow-weak-key-signatures", "@"), ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"), ARGPARSE_s_n (oAllowOldCipherAlgos, "allow-old-cipher-algos", "@"), ARGPARSE_s_s (oWeakDigest, "weak-digest","@"), ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"), ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"), ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"), ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"), ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"), ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"), ARGPARSE_header (NULL, N_("Options for unattended use")), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_n (oNoBatch, "no-batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", "@"), ARGPARSE_s_n (oAnswerNo, "no", "@"), - ARGPARSE_s_i (oStatusFD, "status-fd", "@"), + ARGPARSE_s_s (oStatusFD, "status-fd", "@"), ARGPARSE_s_s (oStatusFile, "status-file", "@"), - ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"), + ARGPARSE_s_s (oAttributeFD, "attribute-fd", "@"), ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"), - ARGPARSE_s_i (oCommandFD, "command-fd", "@"), + ARGPARSE_s_s (oCommandFD, "command-fd", "@"), ARGPARSE_s_s (oCommandFile, "command-file", "@"), ARGPARSE_o_s (oPassphrase, "passphrase", "@"), - ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), + ARGPARSE_s_s (oPassphraseFD, "passphrase-fd", "@"), ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"), ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"), ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), ARGPARSE_s_n (oForceSignKey, "force-sign-key", "@"), ARGPARSE_header (NULL, N_("Other options")), ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), 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_s (oChUid, "chuid", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"), ARGPARSE_s_n (oForbidGenKey, "forbid-gen-key", "@"), ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"), /* Options which can be used in special circumstances. They are not * published and we hope they are never required. */ ARGPARSE_s_n (oUseOnlyOpenPGPCard, "use-only-openpgp-card", "@"), /* Esoteric compatibility options. */ ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"), ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"), ARGPARSE_p_u (oKbxBufferSize, "kbx-buffer-size", "@"), ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"), ARGPARSE_header (NULL, ""), /* Stop the header group. */ /* Aliases. I constantly mistype these, and assume other people do as well. */ ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"), ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"), /* These two are aliases to help users of the PGP command line product use gpg with minimal pain. Many commands are common already as they seem to have borrowed commands from us. Now I'm returning the favor. */ ARGPARSE_s_s (oLocalUser, "sign-with", "@"), ARGPARSE_s_s (oRecipient, "user", "@"), /* Dummy options with warnings. */ ARGPARSE_s_n (oUseAgent, "use-agent", "@"), ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"), ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"), ARGPARSE_s_s (oReaderPort, "reader-port", "@"), ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"), ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"), ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"), ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"), ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"), /* Dummy options. */ ARGPARSE_ignore (oStrict, "strict"), ARGPARSE_ignore (oNoStrict, "no-strict"), ARGPARSE_ignore (oLoadExtension, "load-extension"), /* from 1.4. */ ARGPARSE_s_n (oNoop, "sk-comments", "@"), ARGPARSE_s_n (oNoop, "no-sk-comments", "@"), ARGPARSE_s_n (oNoop, "compress-keys", "@"), ARGPARSE_s_n (oNoop, "compress-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"), ARGPARSE_s_n (oNoop, "force-v4-certs", "@"), ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"), ARGPARSE_s_n (oNoop, "no-mdc-warning", "@"), ARGPARSE_s_n (oNoop, "force-mdc", "@"), ARGPARSE_s_n (oNoop, "no-force-mdc", "@"), ARGPARSE_s_n (oNoop, "disable-mdc", "@"), ARGPARSE_s_n (oNoop, "no-disable-mdc", "@"), ARGPARSE_s_n (oNoop, "allow-multisig-verification", "@"), ARGPARSE_s_n (oNoop, "allow-multiple-messages", "@"), ARGPARSE_s_n (oNoop, "no-allow-multiple-messages", "@"), ARGPARSE_s_s (oNoop, "aead-algo", "@"), ARGPARSE_s_s (oNoop, "personal-aead-preferences","@"), ARGPARSE_s_n (oNoop, "rfc4880bis", "@"), ARGPARSE_s_n (oNoop, "override-compliance-check", "@"), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_group (303, N_("@\nExamples:\n\n" " -se -r Bob [file] sign and encrypt for user Bob\n" " --clear-sign [file] make a clear text signature\n" " --detach-sign [file] make a detached signature\n" " --list-keys [names] show keys\n" " --fingerprint [names] show fingerprints\n")), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_PACKET_VALUE , "packet" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_FILTER_VALUE , "filter" }, { DBG_IOBUF_VALUE , "iobuf" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_TRUST_VALUE , "trust" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* The list of compatibility flags. */ static struct compatibility_flags_s compatibility_flags [] = { { 0, NULL } }; #ifdef ENABLE_SELINUX_HACKS #define ALWAYS_ADD_KEYRINGS 1 #else #define ALWAYS_ADD_KEYRINGS 0 #endif /* The list of the default AKL methods. */ #define DEFAULT_AKL_LIST "local,wkd" /* Can be set to true to force gpg to return with EXIT_FAILURE. */ int g10_errors_seen = 0; /* If opt.assert_signer_list is used and this variabale is not true * gpg will be forced to return EXIT_FAILURE. */ int assert_signer_true = 0; static int utf8_strings = #ifdef HAVE_W32_SYSTEM 1 #else 0 #endif ; static int maybe_setuid = 1; static unsigned int opt_set_iobuf_size; static unsigned int opt_set_iobuf_size_used; static int opt_log_time; /* Collection of options used only in this module. */ static struct { unsigned int forbid_gen_key; } mopt; static char *build_list( const char *text, char letter, const char *(*mapf)(int), int (*chkf)(int) ); static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void print_mds( const char *fname, int algo ); static void add_notation_data( const char *string, int which ); static void add_policy_url( const char *string, int which ); static void add_keyserver_url( const char *string, int which ); static void emergency_cleanup (void); static void read_sessionkey_from_fd (int fd); /* NPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static int build_list_pk_test_algo (int algo) { /* Show only one "RSA" string. If RSA_E or RSA_S is available RSA is also available. */ if (algo == PUBKEY_ALGO_RSA_E || algo == PUBKEY_ALGO_RSA_S) return GPG_ERR_DIGEST_ALGO; return openpgp_pk_test_algo (algo); } static const char * build_list_pk_algo_name (int algo) { return openpgp_pk_algo_name (algo); } static int build_list_cipher_test_algo (int algo) { return openpgp_cipher_test_algo (algo); } static const char * build_list_cipher_algo_name (int algo) { return openpgp_cipher_algo_name (algo); } static int build_list_md_test_algo (int algo) { /* By default we do not accept MD5 based signatures. To avoid confusion we do not announce support for it either. */ if (algo == DIGEST_ALGO_MD5) return GPG_ERR_DIGEST_ALGO; return openpgp_md_test_algo (algo); } static const char * build_list_md_algo_name (int algo) { return openpgp_md_algo_name (algo); } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers, *zips, *ver_gcry; const char *p; switch( level ) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPG@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; #ifdef IS_DEVELOPMENT_VERSION case 25: p="NOTE: THIS IS A DEVELOPMENT VERSION!"; break; case 26: p="It is only intended for test purposes and should NOT be"; break; case 27: p="used in a production environment or with production keys!"; break; #endif case 1: case 40: p = _("Usage: @GPG@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @GPG@ [options] [files]\n" "Sign, check, encrypt or decrypt\n" "Default operation depends on the input data\n"); break; case 31: p = "\nHome: "; break; #ifndef __riscos__ case 32: p = gnupg_homedir (); break; #else /* __riscos__ */ case 32: p = make_filename(gnupg_homedir (), NULL); break; #endif /* __riscos__ */ case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!pubkeys) pubkeys = build_list (_("Pubkey: "), 1, build_list_pk_algo_name, build_list_pk_test_algo ); p = pubkeys; break; case 35: if( !ciphers ) ciphers = build_list(_("Cipher: "), 'S', build_list_cipher_algo_name, build_list_cipher_test_algo ); p = ciphers; break; case 37: if( !digests ) digests = build_list(_("Hash: "), 'H', build_list_md_algo_name, build_list_md_test_algo ); p = digests; break; case 38: if( !zips ) zips = build_list(_("Compression: "),'Z', compress_algo_to_string, check_compress_algo); p = zips; break; case 95: p = "1"; /* <-- Enable globbing under Windows (see init.c) */ break; default: p = NULL; } return p; } static char * build_list (const char *text, char letter, const char * (*mapf)(int), int (*chkf)(int)) { membuf_t mb; int indent; int i, j, len; int limit; const char *s; char *string; if (maybe_setuid) gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ indent = utf8_charcount (text, -1); len = 0; init_membuf (&mb, 512); limit = (letter == 'A')? 4 : 110; for (i=0; i <= limit; i++ ) { if (!chkf (i) && (s = mapf (i))) { if (mb.len - len > 60) { put_membuf_str (&mb, ",\n"); len = mb.len; for (j=0; j < indent; j++) put_membuf_str (&mb, " "); } else if (mb.len) put_membuf_str (&mb, ", "); else put_membuf_str (&mb, text); put_membuf_str (&mb, s); if (opt.verbose && letter) { char num[20]; if (letter == 1) snprintf (num, sizeof num, " (%d)", i); else snprintf (num, sizeof num, " (%c%d)", letter, i); put_membuf_str (&mb, num); } } } if (mb.len) put_membuf_str (&mb, "\n"); put_membuf (&mb, "", 1); string = get_membuf (&mb, NULL); return xrealloc (string, strlen (string)+1); } static void wrong_args( const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text); log_inc_errorcount (); g10_exit(2); } static char * make_username( const char *string ) { char *p; if( utf8_strings ) p = xstrdup(string); else p = native_to_utf8( string ); return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Setup the debugging. With a LEVEL of NULL only the active debug flags are propagated to the subsystems. With LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. */ static void set_debug (const char *level) { int numok = (level && digitp (level)); int numlvl = numok? atoi (level) : 0; if (!level) ; else if (!strcmp (level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_MEMSTAT_VALUE; else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE; else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE |DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE); else if (!strcmp (level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), level); g10_exit (2); } if ((opt.debug & DBG_MEMORY_VALUE)) memory_debug_mode = 1; if ((opt.debug & DBG_MEMSTAT_VALUE)) memory_stat_debug_mode = 1; if (DBG_MPI) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (DBG_CRYPTO) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); if ((opt.debug & DBG_IOBUF_VALUE)) iobuf_debug_mode = 1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); /* Make sure that we are --verbose in debug mode. */ if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt_set_iobuf_size || opt_set_iobuf_size_used) log_debug ("iobuf buffer size is %uk\n", iobuf_set_buffer_size (opt_set_iobuf_size)); } /* We set the screen dimensions for UI purposes. Do not allow screens smaller than 80x24 for the sake of simplicity. */ static void set_screen_dimensions(void) { #ifndef HAVE_W32_SYSTEM char *str; str=getenv("COLUMNS"); if(str) opt.screen_columns=atoi(str); str=getenv("LINES"); if(str) opt.screen_lines=atoi(str); #endif if(opt.screen_columns<80 || opt.screen_columns>255) opt.screen_columns=80; if(opt.screen_lines<24 || opt.screen_lines>255) opt.screen_lines=24; } /* Helper to open a file FNAME either for reading or writing to be used with --status-file etc functions. Not generally useful but it avoids the riscos specific functions and well some Windows people might like it too. Prints an error message and returns -1 on error. On success the file descriptor is returned. */ static int open_info_file (const char *fname, int for_write, int binary) { #ifdef __riscos__ return riscos_fdopenfile (fname, for_write); #elif defined (ENABLE_SELINUX_HACKS) /* We can't allow these even when testing for a secured filename because files to be secured might not yet been secured. This is similar to the option file but in that case it is unlikely that sensitive information may be retrieved by means of error messages. */ (void)fname; (void)for_write; (void)binary; return -1; #else int fd; if (binary) binary = MY_O_BINARY; /* if (is_secured_filename (fname)) */ /* { */ /* fd = -1; */ /* gpg_err_set_errno (EPERM); */ /* } */ /* else */ /* { */ do { if (for_write) fd = gnupg_open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); else fd = gnupg_open (fname, O_RDONLY | binary, 0); } while (fd == -1 && errno == EINTR); /* } */ if ( fd == -1) log_error ( for_write? _("can't create '%s': %s\n") : _("can't open '%s': %s\n"), fname, strerror(errno)); return fd; #endif } static void set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ) { enum cmd_and_opt_values cmd = *ret_cmd; if( !cmd || cmd == new_cmd ) cmd = new_cmd; else if( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if( cmd == aSign && new_cmd == aSym ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aSign ) cmd = aSignSym; else if( cmd == aSym && new_cmd == aEncr ) cmd = aEncrSym; else if( cmd == aEncr && new_cmd == aSym ) cmd = aEncrSym; else if (cmd == aSignEncr && new_cmd == aSym) cmd = aSignEncrSym; else if (cmd == aSignSym && new_cmd == aEncr) cmd = aSignEncrSym; else if (cmd == aEncrSym && new_cmd == aSign) cmd = aSignEncrSym; else if( ( cmd == aSign && new_cmd == aClearsign ) || ( cmd == aClearsign && new_cmd == aSign ) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); g10_exit(2); } *ret_cmd = cmd; } static void add_group(char *string) { char *name,*value; struct groupitem *item; /* Break off the group name */ name=strsep(&string,"="); if(string==NULL) { log_error(_("no = sign found in group definition '%s'\n"),name); return; } trim_trailing_ws(name,strlen(name)); /* Does this group already exist? */ for(item=opt.grouplist;item;item=item->next) if(strcasecmp(item->name,name)==0) break; if(!item) { item=xmalloc(sizeof(struct groupitem)); item->name=name; item->next=opt.grouplist; item->values=NULL; opt.grouplist=item; } /* Break apart the values */ while ((value= strsep(&string," \t"))) { if (*value) add_to_strlist2(&item->values,value,utf8_strings); } } static void rm_group(char *name) { struct groupitem *item,*last=NULL; trim_trailing_ws(name,strlen(name)); for(item=opt.grouplist;item;last=item,item=item->next) { if(strcasecmp(item->name,name)==0) { if(last) last->next=item->next; else opt.grouplist=item->next; free_strlist(item->values); xfree(item); break; } } } /* We need to check three things. 0) The homedir. It must be x00, a directory, and owned by the user. 1) The options/gpg.conf file. Okay unless it or its containing directory is group or other writable or not owned by us. Disable exec in this case. 2) Extensions. Same as #1. Returns true if the item is unsafe. */ static int check_permissions (const char *path, int item) { #if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM) static int homedir_cache=-1; char *tmppath,*dir; struct stat statbuf,dirbuf; int homedir=0,ret=0,checkonly=0; int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0; if(opt.no_perm_warn) return 0; log_assert(item==0 || item==1 || item==2); /* extensions may attach a path */ if(item==2 && path[0]!=DIRSEP_C) { if(strchr(path,DIRSEP_C)) tmppath=make_filename(path,NULL); else tmppath=make_filename(gnupg_libdir (),path,NULL); } else tmppath=xstrdup(path); /* If the item is located in the homedir, but isn't the homedir, don't continue if we already checked the homedir itself. This is to avoid user confusion with an extra options file warning which could be rectified if the homedir itself had proper permissions. */ if(item!=0 && homedir_cache>-1 && !ascii_strncasecmp (gnupg_homedir (), tmppath, strlen (gnupg_homedir ()))) { ret=homedir_cache; goto end; } /* It's okay if the file or directory doesn't exist */ if (gnupg_stat (tmppath,&statbuf)) { ret=0; goto end; } /* Now check the enclosing directory. Theoretically, we could walk this test up to the root directory /, but for the sake of sanity, I'm stopping at one level down. */ dir=make_dirname(tmppath); if (gnupg_stat (dir,&dirbuf) || !S_ISDIR (dirbuf.st_mode)) { /* Weird error */ xfree(dir); ret=1; goto end; } xfree(dir); /* Assume failure */ ret=1; if(item==0) { /* The homedir must be x00, a directory, and owned by the user. */ if(S_ISDIR(statbuf.st_mode)) { if(statbuf.st_uid==getuid()) { if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=1; } else own=1; homedir_cache=ret; } } else if(item==1 || item==2) { /* The options or extension file. Okay unless it or its containing directory is group or other writable or not owned by us or root. */ if(S_ISREG(statbuf.st_mode)) { if(statbuf.st_uid==getuid() || statbuf.st_uid==0) { if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0) { /* it's not writable, so make sure the enclosing directory is also not writable */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0) ret=0; else enc_dir_perm=1; } else enc_dir_own=1; } else { /* it's writable, so the enclosing directory had better not let people get to it. */ if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0) { if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0) ret=0; else perm=enc_dir_perm=1; /* unclear which one to fix! */ } else enc_dir_own=1; } } else own=1; } } else BUG(); if(!checkonly) { if(own) { if(item==0) log_info(_("WARNING: unsafe ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe ownership on" " extension '%s'\n"),tmppath); } if(perm) { if(item==0) log_info(_("WARNING: unsafe permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe permissions on" " extension '%s'\n"),tmppath); } if(enc_dir_own) { if(item==0) log_info(_("WARNING: unsafe enclosing directory ownership on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory ownership on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory ownership on" " extension '%s'\n"),tmppath); } if(enc_dir_perm) { if(item==0) log_info(_("WARNING: unsafe enclosing directory permissions on" " homedir '%s'\n"),tmppath); else if(item==1) log_info(_("WARNING: unsafe enclosing directory permissions on" " configuration file '%s'\n"),tmppath); else log_info(_("WARNING: unsafe enclosing directory permissions on" " extension '%s'\n"),tmppath); } } end: xfree(tmppath); if(homedir) homedir_cache=ret; return ret; #else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ (void)path; (void)item; return 0; #endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/ } /* Print the OpenPGP defined algo numbers. */ static void print_algo_numbers(int (*checker)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%d",i); } } } static void print_algo_names(int (*checker)(int),const char *(*mapper)(int)) { int i,first=1; for(i=0;i<=110;i++) { if(!checker(i)) { if(first) first=0; else es_printf (";"); es_printf ("%s",mapper(i)); } } } /* In the future, we can do all sorts of interesting configuration output here. For now, just give "group" as the Enigmail folks need it, and pubkey, cipher, hash, and compress as they may be useful for frontends. */ static void list_config(char *items) { int show_all = !items; char *name = NULL; const char *s; struct groupitem *giter; int first, iter; if(!opt.with_colons) return; while(show_all || (name=strsep(&items," "))) { int any=0; if(show_all || ascii_strcasecmp(name,"group")==0) { for (giter = opt.grouplist; giter; giter = giter->next) { strlist_t sl; es_fprintf (es_stdout, "cfg:group:"); es_write_sanitized (es_stdout, giter->name, strlen(giter->name), ":", NULL); es_putc (':', es_stdout); for(sl=giter->values; sl; sl=sl->next) { es_write_sanitized (es_stdout, sl->d, strlen (sl->d), ":;", NULL); if(sl->next) es_printf(";"); } es_printf("\n"); } any=1; } if(show_all || ascii_strcasecmp(name,"version")==0) { es_printf("cfg:version:"); es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkey")==0) { es_printf ("cfg:pubkey:"); print_algo_numbers (build_list_pk_test_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"pubkeyname")==0) { es_printf ("cfg:pubkeyname:"); print_algo_names (build_list_pk_test_algo, build_list_pk_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"cipher")==0) { es_printf ("cfg:cipher:"); print_algo_numbers (build_list_cipher_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp (name,"ciphername")) { es_printf ("cfg:ciphername:"); print_algo_names (build_list_cipher_test_algo, build_list_cipher_algo_name); es_printf ("\n"); any = 1; } if(show_all || ascii_strcasecmp(name,"digest")==0 || ascii_strcasecmp(name,"hash")==0) { es_printf ("cfg:digest:"); print_algo_numbers (build_list_md_test_algo); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"digestname") || !ascii_strcasecmp(name,"hashname")) { es_printf ("cfg:digestname:"); print_algo_names (build_list_md_test_algo, build_list_md_algo_name); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp(name,"compress")==0) { es_printf ("cfg:compress:"); print_algo_numbers(check_compress_algo); es_printf ("\n"); any=1; } if(show_all || ascii_strcasecmp (name, "compressname") == 0) { es_printf ("cfg:compressname:"); print_algo_names (check_compress_algo, compress_algo_to_string); es_printf ("\n"); any=1; } if (show_all || !ascii_strcasecmp(name,"ccid-reader-id")) { /* We ignore this for GnuPG 1.4 backward compatibility. */ any=1; } if (show_all || !ascii_strcasecmp (name,"curve")) { es_printf ("cfg:curve:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0) es_printf ("%s%s", first?"":";", s); es_printf ("\n"); any=1; } /* Curve OIDs are rarely useful and thus only printed if requested. */ if (name && !ascii_strcasecmp (name,"curveoid")) { es_printf ("cfg:curveoid:"); for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0) { s = openpgp_curve_to_oid (s, NULL, NULL); es_printf ("%s%s", first?"":";", s? s:"[?]"); } es_printf ("\n"); any=1; } if(show_all) break; if(!any) log_error(_("unknown configuration item '%s'\n"),name); } } /* List default values for use by gpgconf. */ static void gpgconf_list (void) { es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg"); /* The next one is an info only item and should match the macros at the top of keygen.c */ es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, get_default_pubkey_algo ()); /* This info only mode tells whether the we are running in de-vs * compliance mode. This does not test all parameters but the basic * conditions like a proper RNG and Libgcrypt. AS of now we always * return 0 because this version of gnupg has not yet received an * appoval. */ es_printf ("compliance_de_vs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0 /*gnupg_rng_is_compliant (CO_DE_VS)*/); es_printf ("use_keyboxd:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, opt.use_keyboxd); } static int parse_subpacket_list(char *list) { char *tok; byte subpackets[128],i; int count=0; if(!list) { /* No arguments means all subpackets */ memset(subpackets+1,1,sizeof(subpackets)-1); count=127; } else { memset(subpackets,0,sizeof(subpackets)); /* Merge with earlier copy */ if(opt.show_subpackets) { byte *in; for(in=opt.show_subpackets;*in;in++) { if(*in>127 || *in<1) BUG(); if(!subpackets[*in]) count++; subpackets[*in]=1; } } while((tok=strsep(&list," ,"))) { if(!*tok) continue; i=atoi(tok); if(i>127 || i<1) return 0; if(!subpackets[i]) count++; subpackets[i]=1; } } xfree(opt.show_subpackets); opt.show_subpackets=xmalloc(count+1); opt.show_subpackets[count--]=0; for(i=1;i<128 && count>=0;i++) if(subpackets[i]) opt.show_subpackets[count--]=i; return 1; } static int parse_list_options(char *str) { char *subpackets=""; /* something that isn't NULL */ struct parse_options lopts[]= { {"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL, NULL}, {"show-photos",LIST_SHOW_PHOTOS,NULL, N_("display photo IDs during key listings")}, {"show-usage",LIST_SHOW_USAGE,NULL, N_("show key usage information during key listings")}, {"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature listings")}, {"show-notations",LIST_SHOW_NOTATIONS,NULL, N_("show all notations during signature listings")}, {"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature listings")}, {"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature listings")}, {"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature listings")}, {"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during key listings")}, {"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in key listings")}, {"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL, N_("show revoked and expired subkeys in key listings")}, {"show-unusable-sigs",LIST_SHOW_UNUSABLE_SIGS,NULL, N_("show signatures with invalid algorithms during signature listings")}, {"show-keyring",LIST_SHOW_KEYRING,NULL, N_("show the keyring name in key listings")}, {"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL, N_("show expiration dates during signature listings")}, {"show-pref", LIST_SHOW_PREF, NULL, N_("show preferences")}, {"show-pref-verbose", LIST_SHOW_PREF_VERBOSE, NULL, N_("show preferences")}, {"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL, NULL}, {"sort-sigs", LIST_SORT_SIGS, NULL, NULL}, {NULL,0,NULL,NULL} }; int i; /* C99 allows for non-constant initializers, but we'd like to compile everywhere, so fill in the show-sig-subpackets argument here. Note that if the parse_options array changes, we'll have to change the subscript here. We use a loop here in case the list above is reordered. */ for (i=0; lopts[i].name; i++) if (lopts[i].bit == LIST_SHOW_SIG_SUBPACKETS) { lopts[i].value = &subpackets; break; } if(parse_options(str,&opt.list_options,lopts,1)) { if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS) { /* Unset so users can pass multiple lists in. */ opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS; if(!parse_subpacket_list(subpackets)) return 0; } else if(subpackets==NULL && opt.show_subpackets) { /* User did 'no-show-subpackets' */ xfree(opt.show_subpackets); opt.show_subpackets=NULL; } return 1; } else return 0; } /* Collapses argc/argv into a single string that must be freed */ static char * collapse_args(int argc,char *argv[]) { char *str=NULL; int i,first=1,len=0; for(i=0;imagic = SERVER_CONTROL_MAGIC; } /* This function is called to deinitialize a control object. It is not deallocated. */ static void gpg_deinit_default_ctrl (ctrl_t ctrl) { #ifdef USE_TOFU tofu_closedbs (ctrl); #endif gpg_dirmngr_deinit_session_data (ctrl); keydb_release (ctrl->cached_getkey_kdb); gpg_keyboxd_deinit_session_data (ctrl); xfree (ctrl->secret_keygrips); ctrl->secret_keygrips = NULL; } int main (int argc, char **argv) { gpgrt_argparse_t pargs; IOBUF a; int rc=0; int orig_argc; char **orig_argv; const char *fname; char *username; int may_coredump; strlist_t sl; strlist_t remusr = NULL; strlist_t locusr = NULL; strlist_t nrings = NULL; armor_filter_context_t *afx = NULL; int detached_sig = 0; char *last_configname = NULL; const char *configname = NULL; /* NULL or points to last_configname. * NULL also indicates that we are * processing options from the cmdline. */ int debug_argparser = 0; int default_keyring = 1; int greeting = 0; int nogreeting = 0; char *logfile = NULL; int use_random_seed = 1; enum cmd_and_opt_values cmd = 0; const char *debug_level = NULL; #ifndef NO_TRUST_MODELS const char *trustdb_name = NULL; #endif /*!NO_TRUST_MODELS*/ char *def_cipher_string = NULL; char *def_digest_string = NULL; char *compress_algo_string = NULL; char *cert_digest_string = NULL; char *s2k_cipher_string = NULL; char *s2k_digest_string = NULL; char *pers_cipher_list = NULL; char *pers_digest_list = NULL; char *pers_compress_list = NULL; int eyes_only=0; int multifile=0; int pwfd = -1; int ovrseskeyfd = -1; int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */ int any_explicit_recipient = 0; int default_akl = 1; int require_secmem = 0; int got_secmem = 0; struct assuan_malloc_hooks malloc_hooks; ctrl_t ctrl; static int print_dane_records; static int allow_large_chunks; static const char *homedirvalue; static const char *changeuser; #ifdef __riscos__ opt.lock_once = 1; #endif /* __riscos__ */ /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to secmem_init() somewhere after the option parsing. */ early_system_init (); gnupg_reopen_std (GPG_NAME); trap_unaligned (); gnupg_rl_initialize (); gpgrt_set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Use our own logging handler for Libcgrypt. */ setup_libgcrypt_logging (); /* Put random number into secure memory */ gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps(); gnupg_init_signals (0, emergency_cleanup); dotlock_create (NULL, 0); /* Register lock file cleanup. */ /* Tell the compliance module who we are. */ gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); opt.command_fd = -1; /* no command fd */ opt.compress_level = -1; /* defaults to standard compress level */ opt.bz2_compress_level = -1; /* defaults to standard compress level */ /* note: if you change these lines, look at oOpenPGP */ opt.def_cipher_algo = 0; opt.def_digest_algo = 0; opt.cert_digest_algo = 0; opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */ opt.s2k_mode = 3; /* iterated+salted */ opt.s2k_count = 0; /* Auto-calibrate when needed. */ opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO; opt.completes_needed = 1; opt.marginals_needed = 3; opt.max_cert_depth = 5; opt.escape_from = 1; opt.flags.require_cross_cert = 1; opt.import_options = (IMPORT_REPAIR_KEYS | IMPORT_COLLAPSE_UIDS | IMPORT_COLLAPSE_SUBKEYS); opt.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS | IMPORT_REPAIR_PKS_SUBKEY_BUG | IMPORT_SELF_SIGS_ONLY | IMPORT_COLLAPSE_UIDS | IMPORT_COLLAPSE_SUBKEYS | IMPORT_CLEAN); opt.keyserver_options.export_options = EXPORT_ATTRIBUTES; opt.keyserver_options.options = 0; opt.verify_options = (LIST_SHOW_UID_VALIDITY | VERIFY_SHOW_POLICY_URLS | VERIFY_SHOW_STD_NOTATIONS | VERIFY_SHOW_KEYSERVER_URLS); opt.list_options = (LIST_SHOW_UID_VALIDITY | LIST_SORT_SIGS | LIST_SHOW_USAGE); #ifdef NO_TRUST_MODELS opt.trust_model = TM_ALWAYS; #else opt.trust_model = TM_AUTO; #endif opt.tofu_default_policy = TOFU_POLICY_AUTO; opt.mangle_dos_filenames = 0; opt.min_cert_level = 2; set_screen_dimensions (); opt.keyid_format = KF_NONE; opt.def_sig_expire = "0"; opt.def_cert_expire = "0"; opt.passphrase_repeat = 1; opt.emit_version = 0; opt.weak_digests = NULL; opt.compliance = CO_GNUPG; /* Check special options given on the command line. */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oDebugIOLBF: es_setvbuf (es_stdout, NULL, _IOLBF, 0); break; case oNoOptions: /* Set here here because the homedir would otherwise be * created before main option parsing starts. */ opt.no_homedir_creation = 1; break; case oHomedir: homedirvalue = pargs.r.ret_str; break; case oChUid: changeuser = pargs.r.ret_str; break; case oNoPermissionWarn: opt.no_perm_warn = 1; break; } } /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); #ifdef HAVE_DOSISH_SYSTEM /* FIXME: Do we still need this? No: gnupg_homedir calls * make_filename which changes the slashed anyway. IsDBCSLeadByte still * needed? See bug #561. */ if ( strchr (gnupg_homedir (), '\\') ) { char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1); const char *s; for (d=buf, s = gnupg_homedir (); *s; s++) { *d++ = *s == '\\'? '/': *s; #ifdef HAVE_W32_SYSTEM if (s[1] && IsDBCSLeadByte (*s)) *d++ = *++s; #endif } *d = 0; gnupg_set_homedir (buf); } #endif /* Initialize the secure memory. */ if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0)) got_secmem = 1; #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) /* There should be no way to get to this spot while still carrying setuid privs. Just in case, bomb out if we are. */ if ( getuid () != geteuid () ) BUG (); #endif maybe_setuid = 0; /* Okay, we are now working under our real uid */ /* malloc hooks go here ... */ 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); setup_libassuan_logging (&opt.debug, NULL); /* Change UID and then set the homedir. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ gnupg_set_homedir (homedirvalue); /* Set default options which require that malloc stuff is ready. */ additional_weak_digest ("MD5"); parse_auto_key_locate (DEFAULT_AKL_LIST); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; /* We are re-using the struct, thus the reset flag. We OR the * flags so that the internal intialized flag won't be cleared. */ pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER | ARGPARSE_FLAG_USERVERS); /* By this point we have a homedir, and cannot change it. */ check_permissions (gnupg_homedir (), 0); /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); while (gpgrt_argparser (&pargs, opts, GPG_NAME EXTSEP_S "conf" )) { switch (pargs.r_opt) { case ARGPARSE_CONFFILE: if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; if (is_secured_filename (configname)) { pargs.r_opt = ARGPARSE_PERMISSION_ERROR; pargs.err = ARGPARSE_PRINT_ERROR; } else if (strncmp (configname, gnupg_sysconfdir (), strlen (gnupg_sysconfdir ()))) { /* This is not the global config file and thus we * need to check the permissions: If the file is * unsafe, then disable any external programs for * keyserver calls or photo IDs. Since the * external program to call is set in the options * file, a unsafe options file can lead to an * arbitrary program being run. */ if (check_permissions (configname, 1)) opt.exec_disable=1; } } else configname = NULL; break; /* case oOptions: */ /* case oNoOptions: */ /* We will never see these options here because * gpgrt_argparse handles them for us. */ /* break */ case aListConfig: case aListGcryptConfig: case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); /* Do not register a keyring for these commands. */ default_keyring = -1; break; case aCheckKeys: case aListPackets: case aImport: case aFastImport: case aSendKeys: case aRecvKeys: case aSearchKeys: case aRefreshKeys: case aFetchKeys: case aExport: #ifdef ENABLE_CARD_SUPPORT case aCardStatus: case aCardEdit: case aChangePIN: #endif /* ENABLE_CARD_SUPPORT*/ case aListKeys: case aLocateKeys: case aLocateExtKeys: case aListSigs: case aExportSecret: case aExportSecretSub: case aExportSshKey: case aExportSecretSshKey: case aSym: case aClearsign: case aGenRevoke: case aDesigRevoke: case aPrimegen: case aGenRandom: case aPrintMD: case aPrintMDs: case aListTrustDB: case aCheckTrustDB: case aUpdateTrustDB: case aFixTrustDB: case aListTrustPath: case aDeArmor: case aEnArmor: case aSign: case aQuickSignKey: case aQuickLSignKey: case aQuickRevSig: case aSignKey: case aLSignKey: case aStore: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickAddADSK: case aQuickRevUid: case aQuickSetExpire: case aQuickSetPrimaryUid: case aQuickUpdatePref: case aExportOwnerTrust: case aImportOwnerTrust: case aRebuildKeydbCaches: set_cmd (&cmd, pargs.r_opt); break; case aKeygen: case aFullKeygen: case aEditKey: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aDeleteKeys: case aPasswd: set_cmd (&cmd, pargs.r_opt); greeting=1; break; case aShowKeys: set_cmd (&cmd, pargs.r_opt); opt.import_options |= IMPORT_SHOW; opt.import_options |= IMPORT_DRY_RUN; opt.import_options &= ~IMPORT_REPAIR_KEYS; opt.list_options |= LIST_SHOW_UNUSABLE_UIDS; opt.list_options |= LIST_SHOW_UNUSABLE_SUBKEYS; opt.list_options |= LIST_SHOW_NOTATIONS; opt.list_options |= LIST_SHOW_POLICY_URLS; break; case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break; case aDecryptFiles: multifile=1; /* fall through */ case aDecrypt: set_cmd( &cmd, aDecrypt); break; case aEncrFiles: multifile=1; /* fall through */ case aEncr: set_cmd( &cmd, aEncr); break; case aVerifyFiles: multifile=1; /* fall through */ case aVerify: set_cmd( &cmd, aVerify); break; case aServer: set_cmd (&cmd, pargs.r_opt); opt.batch = 1; break; case aTOFUPolicy: set_cmd (&cmd, pargs.r_opt); break; case oArmor: opt.armor = 1; opt.no_armor=0; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break; case oInputSizeHint: opt.input_size_hint = string_to_u64 (pargs.r.ret_str); break; case oChunkSize: opt.chunk_size = pargs.r.ret_int; break; case oQuiet: opt.quiet = 1; break; case oNoTTY: tty_no_terminal(1); break; case oDryRun: opt.dry_run = 1; break; case oInteractive: opt.interactive = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_options|=LIST_SHOW_UNUSABLE_UIDS; opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS; break; case oBatch: opt.batch = 1; nogreeting = 1; break; case oUseAgent: /* Dummy. */ break; case oNoUseAgent: obsolete_option (configname, pargs.lineno, "no-use-agent"); break; case oGpgAgentInfo: obsolete_option (configname, pargs.lineno, "gpg-agent-info"); break; case oUseKeyboxd: opt.use_keyboxd = 1; break; case oReaderPort: obsolete_scdaemon_option (configname, pargs.lineno, "reader-port"); break; case octapiDriver: obsolete_scdaemon_option (configname, pargs.lineno, "ctapi-driver"); break; case opcscDriver: obsolete_scdaemon_option (configname, pargs.lineno, "pcsc-driver"); break; case oDisableCCID: obsolete_scdaemon_option (configname, pargs.lineno, "disable-ccid"); break; case oHonorHttpProxy: obsolete_option (configname, pargs.lineno, "honor-http-proxy"); break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oForceSignKey: opt.flags.force_sign_key = 1; break; case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; case oPrimaryKeyring: sl = append_to_strlist (&nrings, pargs.r.ret_str); sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY; break; case oShowKeyring: deprecated_warning(configname,pargs.lineno,"--show-keyring", "--list-options ","show-keyring"); opt.list_options|=LIST_SHOW_KEYRING; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugIOLBF: break; /* Already set in pre-parse step. */ case oDebugSetIobufSize: opt_set_iobuf_size = pargs.r.ret_ulong; opt_set_iobuf_size_used = 1; break; case oDebugAllowLargeChunks: allow_large_chunks = 1; break; case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break; case oCompatibilityFlags: if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags, compatibility_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oStatusFD: - set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); + set_status_fd ( translate_sys2libc_fdstr (pargs.r.ret_str, 1) ); break; case oStatusFile: set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) ); break; case oAttributeFD: - set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) ); + set_attrib_fd ( translate_sys2libc_fdstr (pargs.r.ret_str, 1) ); break; case oAttributeFile: set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) ); break; case oLoggerFD: - log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + log_set_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oLoggerFile: logfile = pargs.r.ret_str; break; case oLogTime: opt_log_time = 1; break; case oWithFingerprint: opt.with_fingerprint = 1; opt.fingerprint++; break; case oWithSubkeyFingerprint: opt.with_subkey_fingerprint = 1; break; case oWithICAOSpelling: opt.with_icao_spelling = 1; break; case oFingerprint: opt.fingerprint++; fpr_maybe_cmd = 1; break; case oWithKeygrip: opt.with_keygrip = 1; break; case oWithKeyScreening: opt.with_key_screening = 1; break; case oWithSecret: opt.with_secret = 1; break; case oWithWKDHash: opt.with_wkd_hash = 1; break; case oWithKeyOrigin: opt.with_key_origin = 1; break; case oSecretKeyring: obsolete_option (configname, pargs.lineno, "secret-keyring"); break; case oNoArmor: opt.no_armor=1; opt.armor=0; break; case oNoDefKeyring: if (default_keyring > 0) default_keyring = 0; break; case oNoKeyring: default_keyring = -1; break; case oNoGreeting: nogreeting = 1; break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); opt.list_sigs=0; break; case oQuickRandom: gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); break; case oEmitVersion: opt.emit_version++; break; case oNoEmitVersion: opt.emit_version=0; break; case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break; case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break; case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break; #ifndef NO_TRUST_MODELS case oTrustDBName: trustdb_name = pargs.r.ret_str; break; #endif /*!NO_TRUST_MODELS*/ case oDefaultKey: sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configname) sl->flags |= PK_LIST_CONFIG; break; case oDefRecipient: if( *pargs.r.ret_str ) { xfree (opt.def_recipient); opt.def_recipient = make_username(pargs.r.ret_str); } break; case oDefRecipientSelf: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree(opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oHomedir: break; case oChUid: break; /* Command line only (see above). */ case oNoBatch: opt.batch = 0; break; case oWithTofuInfo: opt.with_tofu_info = 1; break; case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/ case oWithColons: opt.with_colons=':'; break; case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/ case oWithSigList: opt.list_sigs = 1; break; case oSkipVerify: opt.skip_verify=1; break; case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break; case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break; case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break; #ifndef NO_TRUST_MODELS /* There are many programs (like mutt) that call gpg with --always-trust so keep this option around for a long time. */ case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break; case oTrustModel: parse_trust_model(pargs.r.ret_str); break; #endif /*!NO_TRUST_MODELS*/ case oTOFUDefaultPolicy: opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str); break; case oTOFUDBFormat: obsolete_option (configname, pargs.lineno, "tofu-db-format"); break; case oForceOwnertrust: log_info(_("Note: %s is not for normal use!\n"), "--force-ownertrust"); opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str); if(opt.force_ownertrust==-1) { log_error("invalid ownertrust '%s'\n",pargs.r.ret_str); opt.force_ownertrust=0; } break; case oNoAutoTrustNewKey: opt.flags.no_auto_trust_new_key = 1; break; case oCompliance: { int compliance = gnupg_parse_compliance_option (pargs.r.ret_str, compliance_options, DIM (compliance_options), opt.quiet); if (compliance < 0) g10_exit (1); set_compliance_option (compliance); } break; case oOpenPGP: case oRFC2440: case oRFC4880: case oPGP7: case oPGP8: case oGnuPG: set_compliance_option (pargs.r_opt); break; case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; case oRFC2440Text: opt.rfc2440_text=1; break; case oNoRFC2440Text: opt.rfc2440_text=0; break; case oSetFilename: if(utf8_strings) opt.set_filename = pargs.r.ret_str; else opt.set_filename = native_to_utf8(pargs.r.ret_str); break; case oForYourEyesOnly: eyes_only = 1; break; case oNoForYourEyesOnly: eyes_only = 0; break; case oSetPolicyURL: add_policy_url(pargs.r.ret_str,0); add_policy_url(pargs.r.ret_str,1); break; case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break; case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break; case oShowPolicyURL: deprecated_warning(configname,pargs.lineno,"--show-policy-url", "--list-options ","show-policy-urls"); deprecated_warning(configname,pargs.lineno,"--show-policy-url", "--verify-options ","show-policy-urls"); opt.list_options|=LIST_SHOW_POLICY_URLS; opt.verify_options|=VERIFY_SHOW_POLICY_URLS; break; case oNoShowPolicyURL: deprecated_warning(configname,pargs.lineno,"--no-show-policy-url", "--list-options ","no-show-policy-urls"); deprecated_warning(configname,pargs.lineno,"--no-show-policy-url", "--verify-options ","no-show-policy-urls"); opt.list_options&=~LIST_SHOW_POLICY_URLS; opt.verify_options&=~VERIFY_SHOW_POLICY_URLS; break; case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break; case oUseEmbeddedFilename: opt.flags.use_embedded_filename=1; break; case oNoUseEmbeddedFilename: opt.flags.use_embedded_filename=0; break; case oComment: if(pargs.r.ret_str[0]) append_to_strlist(&opt.comments,pargs.r.ret_str); break; case oDefaultComment: deprecated_warning(configname,pargs.lineno, "--default-comment","--no-comments",""); /* fall through */ case oNoComments: free_strlist(opt.comments); opt.comments=NULL; break; case oThrowKeyids: opt.throw_keyids = 1; break; case oNoThrowKeyids: opt.throw_keyids = 0; break; case oShowPhotos: deprecated_warning(configname,pargs.lineno,"--show-photos", "--list-options ","show-photos"); deprecated_warning(configname,pargs.lineno,"--show-photos", "--verify-options ","show-photos"); opt.list_options|=LIST_SHOW_PHOTOS; opt.verify_options|=VERIFY_SHOW_PHOTOS; break; case oNoShowPhotos: deprecated_warning(configname,pargs.lineno,"--no-show-photos", "--list-options ","no-show-photos"); deprecated_warning(configname,pargs.lineno,"--no-show-photos", "--verify-options ","no-show-photos"); opt.list_options&=~LIST_SHOW_PHOTOS; opt.verify_options&=~VERIFY_SHOW_PHOTOS; break; case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break; case oForceAEAD: opt.force_aead = 1; break; case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break; case oIncludeKeyBlock: opt.flags.include_key_block = 1; break; case oNoIncludeKeyBlock: opt.flags.include_key_block = 0; break; case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break; case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break; case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break; case oS2KCount: if (pargs.r.ret_int) opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int); else opt.s2k_count = 0; /* Auto-calibrate when needed. */ break; case oRecipient: case oHiddenRecipient: case oRecipientFile: case oHiddenRecipientFile: /* Store the recipient. Note that we also store the * option as private data in the flags. This is achieved * by shifting the option value to the left so to keep * enough space for the flags. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configname) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenRecipient || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_HIDDEN; if (pargs.r_opt == oRecipientFile || pargs.r_opt == oHiddenRecipientFile) sl->flags |= PK_LIST_FROM_FILE; any_explicit_recipient = 1; break; case oEncryptTo: case oHiddenEncryptTo: /* Store an additional recipient. */ sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings ); sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO); if (configname) sl->flags |= PK_LIST_CONFIG; if (pargs.r_opt == oHiddenEncryptTo) sl->flags |= PK_LIST_HIDDEN; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptToDefaultKey: opt.encrypt_to_default_key = configname ? 2 : 1; break; case oTrySecretKey: add_to_strlist2 (&opt.secret_keys_to_try, pargs.r.ret_str, utf8_strings); break; case oMimemode: opt.mimemode = opt.textmode = 1; break; case oTextmodeShort: opt.textmode = 2; break; case oTextmode: opt.textmode=1; break; case oNoTextmode: opt.textmode=opt.mimemode=0; break; case oExpert: opt.expert = 1; break; case oNoExpert: opt.expert = 0; break; case oDefSigExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_sig_expire=pargs.r.ret_str; } break; case oAskSigExpire: opt.ask_sig_expire = 1; break; case oNoAskSigExpire: opt.ask_sig_expire = 0; break; case oDefCertExpire: if(*pargs.r.ret_str!='\0') { if(parse_expire_string(pargs.r.ret_str)==(u32)-1) log_error(_("'%s' is not a valid signature expiration\n"), pargs.r.ret_str); else opt.def_cert_expire=pargs.r.ret_str; } break; case oAskCertExpire: opt.ask_cert_expire = 1; break; case oNoAskCertExpire: opt.ask_cert_expire = 0; break; case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break; case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break; case oAskCertLevel: opt.ask_cert_level = 1; break; case oNoAskCertLevel: opt.ask_cert_level = 0; break; case oLocalUser: /* store the local users */ sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings ); sl->flags = (pargs.r_opt << PK_LIST_SHIFT); if (configname) sl->flags |= PK_LIST_CONFIG; break; case oSender: { char *mbox = mailbox_from_userid (pargs.r.ret_str, 0); if (!mbox) log_error (_("\"%s\" is not a proper mail address\n"), pargs.r.ret_str); else { add_to_strlist (&opt.sender_list, mbox); xfree (mbox); } } break; case oCompress: /* this is the -z command line option */ opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int; opt.explicit_compress_option = 1; break; case oCompressLevel: opt.compress_level = pargs.r.ret_int; break; case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break; case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break; case oPassphrase: set_passphrase_from_string (pargs.r_type ? pargs.r.ret_str : ""); break; case oPassphraseFD: - pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + pwfd = translate_sys2libc_fdstr (pargs.r.ret_str, 0); break; case oPassphraseFile: pwfd = open_info_file (pargs.r.ret_str, 0, 1); break; case oPassphraseRepeat: opt.passphrase_repeat = pargs.r.ret_int; break; case oPinentryMode: opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); if (opt.pinentry_mode == -1) log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); break; case oRequestOrigin: opt.request_origin = parse_request_origin (pargs.r.ret_str); if (opt.request_origin == -1) log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str); break; case oCommandFD: - opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + opt.command_fd = translate_sys2libc_fdstr (pargs.r.ret_str, 0); if (! gnupg_fd_valid (opt.command_fd)) log_error ("command-fd is invalid: %s\n", strerror (errno)); break; case oCommandFile: opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1); break; case oCipherAlgo: def_cipher_string = xstrdup(pargs.r.ret_str); break; case oDigestAlgo: def_digest_string = xstrdup(pargs.r.ret_str); break; case oCompressAlgo: /* If it is all digits, stick a Z in front of it for later. This is for backwards compatibility with versions that took the compress algorithm number. */ { char *pt=pargs.r.ret_str; while(*pt) { if (!isascii (*pt) || !isdigit (*pt)) break; pt++; } if(*pt=='\0') { compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2); strcpy(compress_algo_string,"Z"); strcat(compress_algo_string,pargs.r.ret_str); } else compress_algo_string = xstrdup(pargs.r.ret_str); } break; case oCertDigestAlgo: cert_digest_string = xstrdup(pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oRequireSecmem: require_secmem=1; break; case oNoRequireSecmem: require_secmem=0; break; case oNoPermissionWarn: opt.no_perm_warn=1; break; case oDisplayCharset: if( set_native_charset( pargs.r.ret_str ) ) log_error(_("'%s' is not a valid character set\n"), pargs.r.ret_str); break; case oNotDashEscaped: opt.not_dash_escaped = 1; break; case oEscapeFrom: opt.escape_from = 1; break; case oNoEscapeFrom: opt.escape_from = 0; break; case oLockOnce: opt.lock_once = 1; break; case oLockNever: dotlock_disable (); break; case oLockMultiple: #ifndef __riscos__ opt.lock_once = 0; #else /* __riscos__ */ riscos_not_implemented("lock-multiple"); #endif /* __riscos__ */ break; case oKeyServer: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str, 0); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else { /* We only support a single keyserver. Later ones override earlier ones. (Since we parse the config file first and then the command line arguments, the command line takes precedence.) */ if (opt.keyserver) free_keyserver_spec (opt.keyserver); opt.keyserver = keyserver; } } break; case oKeyServerOptions: if(!parse_keyserver_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid keyserver options\n"), configname,pargs.lineno); else log_error(_("invalid keyserver options\n")); } break; case oImportOptions: if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1)) { if(configname) log_error(_("%s:%d: invalid import options\n"), configname,pargs.lineno); else log_error(_("invalid import options\n")); } break; case oImportFilter: rc = parse_and_set_import_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oExportOptions: if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1)) { if(configname) log_error(_("%s:%d: invalid export options\n"), configname,pargs.lineno); else log_error(_("invalid export options\n")); } break; case oExportFilter: rc = parse_and_set_export_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oListFilter: rc = parse_and_set_list_filter (pargs.r.ret_str); if (rc) log_error (_("invalid filter option: %s\n"), gpg_strerror (rc)); break; case oListOptions: if(!parse_list_options(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid list options\n"), configname,pargs.lineno); else log_error(_("invalid list options\n")); } break; case oVerifyOptions: { struct parse_options vopts[]= { {"show-photos",VERIFY_SHOW_PHOTOS,NULL, N_("display photo IDs during signature verification")}, {"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL, N_("show policy URLs during signature verification")}, {"show-notations",VERIFY_SHOW_NOTATIONS,NULL, N_("show all notations during signature verification")}, {"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, N_("show IETF standard notations during signature verification")}, {"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL, NULL}, {"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL, N_("show user-supplied notations during signature verification")}, {"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL, N_("show preferred keyserver URLs during signature verification")}, {"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL, N_("show user ID validity during signature verification")}, {"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL, N_("show revoked and expired user IDs in signature verification")}, {"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL, N_("show only the primary user ID in signature verification")}, {NULL,0,NULL,NULL} }; if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1)) { if(configname) log_error(_("%s:%d: invalid verify options\n"), configname,pargs.lineno); else log_error(_("invalid verify options\n")); } } break; case oTempDir: opt.temp_dir=pargs.r.ret_str; break; case oExecPath: if(set_exec_path(pargs.r.ret_str)) log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str); else opt.exec_path_set=1; break; case oSetNotation: add_notation_data( pargs.r.ret_str, 0 ); add_notation_data( pargs.r.ret_str, 1 ); break; case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break; case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break; case oKnownNotation: register_known_notation (pargs.r.ret_str); break; case oShowNotation: deprecated_warning(configname,pargs.lineno,"--show-notation", "--list-options ","show-notations"); deprecated_warning(configname,pargs.lineno,"--show-notation", "--verify-options ","show-notations"); opt.list_options|=LIST_SHOW_NOTATIONS; opt.verify_options|=VERIFY_SHOW_NOTATIONS; break; case oNoShowNotation: deprecated_warning(configname,pargs.lineno,"--no-show-notation", "--list-options ","no-show-notations"); deprecated_warning(configname,pargs.lineno,"--no-show-notation", "--verify-options ","no-show-notations"); opt.list_options&=~LIST_SHOW_NOTATIONS; opt.verify_options&=~VERIFY_SHOW_NOTATIONS; break; case oUtf8Strings: utf8_strings = 1; break; case oNoUtf8Strings: #ifdef HAVE_W32_SYSTEM utf8_strings = 0; #endif break; case oDisableCipherAlgo: { int algo = string_to_cipher_algo (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oNoSigCache: opt.no_sig_cache = 1; break; case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break; case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break; case oAllowFreeformUID: opt.allow_freeform_uid = 1; break; case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break; case oNoLiteral: opt.no_literal = 1; break; case oSetFilesize: opt.set_filesize = pargs.r.ret_ulong; break; case oFastListMode: opt.fast_list_mode = 1; break; case oFixedListMode: /* Dummy */ break; case oLegacyListMode: opt.legacy_list_mode = 1; break; case oPrintDANERecords: print_dane_records = 1; break; case oListOnly: opt.list_only=1; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oIgnoreValidFrom: opt.ignore_valid_from = 1; break; case oIgnoreCrcError: opt.ignore_crc_error = 1; break; case oIgnoreMDCError: opt.ignore_mdc_error = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oAutoKeyImport: opt.flags.auto_key_import = 1; break; case oNoAutoKeyImport: opt.flags.auto_key_import = 0; break; case oAutoKeyRetrieve: opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; break; case oNoAutoKeyRetrieve: opt.keyserver_options.options &= ~KEYSERVER_AUTO_KEY_RETRIEVE; break; case oShowSessionKey: opt.show_session_key = 1; break; case oOverrideSessionKey: opt.override_session_key = pargs.r.ret_str; break; case oOverrideSessionKeyFD: - ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + ovrseskeyfd = translate_sys2libc_fdstr (pargs.r.ret_str, 0); break; case oMergeOnly: deprecated_warning(configname,pargs.lineno,"--merge-only", "--import-options ","merge-only"); opt.import_options|=IMPORT_MERGE_ONLY; break; case oAllowSecretKeyImport: /* obsolete */ break; case oTryAllSecrets: opt.try_all_secrets = 1; break; case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break; case oEnableSpecialFilenames: enable_special_filenames (); break; case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break; case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break; case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break; case oPreservePermissions: opt.preserve_permissions=1; break; case oDefaultPreferenceList: opt.def_preference_list = pargs.r.ret_str; break; case oDefaultKeyserverURL: { keyserver_spec_t keyserver; keyserver = parse_keyserver_uri (pargs.r.ret_str,1 ); if (!keyserver) log_error (_("could not parse keyserver URL\n")); else free_keyserver_spec (keyserver); opt.def_keyserver_url = pargs.r.ret_str; } break; case oPersonalCipherPreferences: pers_cipher_list=pargs.r.ret_str; break; case oPersonalDigestPreferences: pers_digest_list=pargs.r.ret_str; break; case oPersonalCompressPreferences: pers_compress_list=pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oDisableDirmngr: opt.disable_dirmngr = 1; break; case oWeakDigest: additional_weak_digest(pargs.r.ret_str); break; case oUnwrap: opt.unwrap_encryption = 1; break; case oOnlySignTextIDs: opt.only_sign_text_ids = 1; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs.r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs.r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs.r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = pargs.r.ret_str; break; case oLCmessages: opt.lc_messages = pargs.r.ret_str; break; case oGroup: add_group(pargs.r.ret_str); break; case oUnGroup: rm_group(pargs.r.ret_str); break; case oNoGroups: while(opt.grouplist) { struct groupitem *iter=opt.grouplist; free_strlist(iter->values); opt.grouplist=opt.grouplist->next; xfree(iter); } break; case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break; case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break; case oEnableProgressFilter: opt.enable_progress_filter = 1; break; case oMultifile: multifile=1; break; case oKeyidFormat: if(ascii_strcasecmp(pargs.r.ret_str,"short")==0) opt.keyid_format=KF_SHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0) opt.keyid_format=KF_LONG; else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0) opt.keyid_format=KF_0xSHORT; else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0) opt.keyid_format=KF_0xLONG; else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0) opt.keyid_format = KF_NONE; else log_error("unknown keyid-format '%s'\n",pargs.r.ret_str); break; case oExitOnStatusWriteError: opt.exit_on_status_write_error = 1; break; case oLimitCardInsertTries: opt.limit_card_insert_tries = pargs.r.ret_int; break; case oRequireCrossCert: opt.flags.require_cross_cert=1; break; case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break; case oAutoKeyLocate: if (default_akl) { /* This is the first time --auto-key-locate is seen. * We need to reset the default akl. */ default_akl = 0; release_akl(); } if(!parse_auto_key_locate(pargs.r.ret_str)) { if(configname) log_error(_("%s:%d: invalid auto-key-locate list\n"), configname,pargs.lineno); else log_error(_("invalid auto-key-locate list\n")); } break; case oNoAutoKeyLocate: release_akl(); break; case oKeyOrigin: if(!parse_key_origin (pargs.r.ret_str)) log_error (_("invalid argument for option \"%.50s\"\n"), "--key-origin"); break; case oEnableLargeRSA: #if SECMEM_BUFFER_SIZE >= 65536 opt.flags.large_rsa=1; #else if (configname) log_info("%s:%d: WARNING: gpg not built with large secure " "memory buffer. Ignoring enable-large-rsa\n", configname,pargs.lineno); else log_info("WARNING: gpg not built with large secure " "memory buffer. Ignoring --enable-large-rsa\n"); #endif /* SECMEM_BUFFER_SIZE >= 65536 */ break; case oDisableLargeRSA: opt.flags.large_rsa=0; break; case oEnableDSA2: opt.flags.dsa2=1; break; case oDisableDSA2: opt.flags.dsa2=0; break; case oAllowWeakDigestAlgos: opt.flags.allow_weak_digest_algos = 1; break; case oAllowWeakKeySignatures: opt.flags.allow_weak_key_signatures = 1; break; case oAllowOldCipherAlgos: opt.flags.allow_old_cipher_algos = 1; break; case oFakedSystemTime: { size_t len = strlen (pargs.r.ret_str); int freeze = 0; time_t faked_time; if (len > 0 && pargs.r.ret_str[len-1] == '!') { freeze = 1; pargs.r.ret_str[len-1] = '\0'; } 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, freeze); } break; case oNoAutostart: opt.autostart = 0; break; case oNoSymkeyCache: opt.no_symkey_cache = 1; break; case oDefaultNewKeyAlgo: opt.def_new_key_algo = pargs.r.ret_str; break; case oUseOnlyOpenPGPCard: opt.flags.use_only_openpgp_card = 1; break; case oFullTimestrings: opt.flags.full_timestrings = 1; break; case oForbidGenKey: mopt.forbid_gen_key = 1; break; case oRequireCompliance: opt.flags.require_compliance = 1; break; case oAddDesigRevoker: if (!strcmp (pargs.r.ret_str, "clear")) FREE_STRLIST (opt.desig_revokers); else append_to_strlist (&opt.desig_revokers, pargs.r.ret_str); break; case oAssertSigner: add_to_strlist (&opt.assert_signer_list, pargs.r.ret_str); break; case oKbxBufferSize: keybox_set_buffersize (pargs.r.ret_ulong, 0); break; case oNoop: break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else { pargs.err = ARGPARSE_PRINT_ERROR; /* The argparse function calls a plain exit and thus * we need to print a status here. */ write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); } break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) { write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } /* Process common component options. */ if (parse_comopt (GNUPG_MODULE_NAME_GPG, debug_argparser)) { write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } if (opt.use_keyboxd) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "use-keyboxd"); opt.use_keyboxd = comopt.use_keyboxd; /* Override. */ if (opt.keyboxd_program) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "keyboxd-program"); if (!opt.keyboxd_program && comopt.keyboxd_program) { opt.keyboxd_program = comopt.keyboxd_program; comopt.keyboxd_program = NULL; } if (comopt.no_autostart) opt.autostart = 0; /* The command --gpgconf-list is pretty simple and may be called directly after the option parsing. */ if (cmd == aGPGConfList) { gpgconf_list (); g10_exit (0); } xfree (last_configname); if (print_dane_records) log_error ("invalid option \"%s\"; use \"%s\" instead\n", "--print-dane-records", "--export-options export-dane"); if (log_get_errorcount (0)) { write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } if( nogreeting ) greeting = 0; if( greeting ) { es_fprintf (es_stderr, "%s %s; %s\n", gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14)); es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { const char *s; if((s=gpgrt_strusage(25))) log_info("%s\n",s); if((s=gpgrt_strusage(26))) log_info("%s\n",s); if((s=gpgrt_strusage(27))) log_info("%s\n",s); } #endif /* Init threading which is used by some helper functions. */ npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID )); } else if (opt_log_time) log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY |GPGRT_LOG_WITH_TIME)); if (opt.verbose > 2) log_info ("using character set '%s'\n", get_native_charset ()); if( may_coredump && !opt.quiet ) log_info(_("WARNING: program may create a core file!\n")); if (eyes_only) { if (opt.set_filename) log_info(_("WARNING: %s overrides %s\n"), "--for-your-eyes-only","--set-filename"); opt.set_filename="_CONSOLE"; } if (opt.no_literal) { log_info(_("Note: %s is not for normal use!\n"), "--no-literal"); if (opt.textmode) log_error(_("%s not allowed with %s!\n"), "--textmode", "--no-literal" ); if (opt.set_filename) log_error(_("%s makes no sense with %s!\n"), eyes_only?"--for-your-eyes-only":"--set-filename", "--no-literal" ); } if (opt.set_filesize) log_info(_("Note: %s is not for normal use!\n"), "--set-filesize"); if( opt.batch ) tty_batchmode( 1 ); if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); dump_isotime (tbuf); log_printf ("\n"); } /* 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]); } gcry_control (GCRYCTL_RESUME_SECMEM_WARN); if(require_secmem && !got_secmem) { log_info(_("will not run with insecure memory due to %s\n"), "--require-secmem"); write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit(2); } set_debug (debug_level); if (opt.verbose) /* Print the compatibility flags. */ parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags); gnupg_set_compliance_extra_info (opt.min_rsa_length); if (DBG_CLOCK) log_clock ("start"); /* Do these after the switch(), so they can override settings. */ if (PGP7) { /* That does not anymore work because we have no more support for v3 signatures. */ opt.escape_from=1; opt.ask_sig_expire=0; } else if(PGP8) { opt.escape_from=1; } if( def_cipher_string ) { opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string); xfree(def_cipher_string); def_cipher_string = NULL; if ( openpgp_cipher_test_algo (opt.def_cipher_algo) ) log_error(_("selected cipher algorithm is invalid\n")); } if( def_digest_string ) { opt.def_digest_algo = string_to_digest_algo (def_digest_string); xfree(def_digest_string); def_digest_string = NULL; if ( openpgp_md_test_algo (opt.def_digest_algo) ) log_error(_("selected digest algorithm is invalid\n")); } if( compress_algo_string ) { opt.compress_algo = string_to_compress_algo(compress_algo_string); xfree(compress_algo_string); compress_algo_string = NULL; if( check_compress_algo(opt.compress_algo) ) log_error(_("selected compression algorithm is invalid\n")); } if( cert_digest_string ) { opt.cert_digest_algo = string_to_digest_algo (cert_digest_string); xfree(cert_digest_string); cert_digest_string = NULL; if (openpgp_md_test_algo(opt.cert_digest_algo)) log_error(_("selected certification digest algorithm is invalid\n")); } if( s2k_cipher_string ) { opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string); xfree(s2k_cipher_string); s2k_cipher_string = NULL; if (openpgp_cipher_test_algo (opt.s2k_cipher_algo)) log_error(_("selected cipher algorithm is invalid\n")); } if( s2k_digest_string ) { opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string); xfree(s2k_digest_string); s2k_digest_string = NULL; if (openpgp_md_test_algo(opt.s2k_digest_algo)) log_error(_("selected digest algorithm is invalid\n")); } if( opt.completes_needed < 1 ) log_error(_("completes-needed must be greater than 0\n")); if( opt.marginals_needed < 2 ) log_error(_("marginals-needed must be greater than 1\n")); if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 ) log_error(_("max-cert-depth must be in the range from 1 to 255\n")); if(opt.def_cert_level<0 || opt.def_cert_level>3) log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n")); if( opt.min_cert_level < 1 || opt.min_cert_level > 3 ) log_error(_("invalid min-cert-level; must be 1, 2, or 3\n")); switch( opt.s2k_mode ) { case 0: if (!opt.quiet) log_info(_("Note: simple S2K mode (0) is strongly discouraged\n")); break; case 1: case 3: break; default: log_error(_("invalid S2K mode; must be 0, 1 or 3\n")); } /* This isn't actually needed, but does serve to error out if the string is invalid. */ if(opt.def_preference_list && keygen_set_std_prefs(opt.def_preference_list,0)) log_error(_("invalid default preferences\n")); if(pers_cipher_list && keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM)) log_error(_("invalid personal cipher preferences\n")); if(pers_digest_list && keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH)) log_error(_("invalid personal digest preferences\n")); if(pers_compress_list && keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP)) log_error(_("invalid personal compress preferences\n")); /* Check chunk size. Please fix also the man page if you change * the default. The limits are given by the specs. */ if (!opt.chunk_size) opt.chunk_size = 22; /* Default to the suggested max of 4 MiB. */ else if (opt.chunk_size < 6) { opt.chunk_size = 6; log_info (_("chunk size invalid - using %d\n"), opt.chunk_size); } else if (opt.chunk_size > (allow_large_chunks? 62 : 22)) { opt.chunk_size = (allow_large_chunks? 62 : 22); log_info (_("chunk size invalid - using %d\n"), opt.chunk_size); } /* We don't support all possible commands with multifile yet */ if(multifile) { char *cmdname; switch(cmd) { case aSign: cmdname="--sign"; break; case aSignEncr: cmdname="--sign --encrypt"; break; case aClearsign: cmdname="--clear-sign"; break; case aDetachedSign: cmdname="--detach-sign"; break; case aSym: cmdname="--symmetric"; break; case aEncrSym: cmdname="--symmetric --encrypt"; break; case aStore: cmdname="--store"; break; default: cmdname=NULL; break; } if(cmdname) log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile"); } if( log_get_errorcount(0) ) { write_status_failure ("option-postprocessing", gpg_error(GPG_ERR_GENERAL)); g10_exit (2); } if(opt.compress_level==0) opt.compress_algo=COMPRESS_ALGO_NONE; /* Check our chosen algorithms against the list of legal algorithms. */ if(!GNUPG) { const char *badalg=NULL; preftype_t badtype=PREFTYPE_NONE; if(opt.def_cipher_algo && !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL)) { badalg = openpgp_cipher_algo_name (opt.def_cipher_algo); badtype = PREFTYPE_SYM; } else if(opt.def_digest_algo && !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.def_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.cert_digest_algo && !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL)) { badalg = gcry_md_algo_name (opt.cert_digest_algo); badtype = PREFTYPE_HASH; } else if(opt.compress_algo!=-1 && !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL)) { badalg = compress_algo_to_string(opt.compress_algo); badtype = PREFTYPE_ZIP; } if(badalg) { switch(badtype) { case PREFTYPE_SYM: log_info (_("cipher algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; case PREFTYPE_HASH: log_info (_("digest algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; case PREFTYPE_ZIP: log_info (_("compression algorithm '%s'" " may not be used in %s mode\n"), badalg, gnupg_compliance_option_string (opt.compliance)); break; default: BUG(); } compliance_failure(); } } /* Check our chosen algorithms against the list of allowed * algorithms in the current compliance mode, and fail hard if it * is not. This is us being nice to the user informing her early * that the chosen algorithms are not available. We also check * and enforce this right before the actual operation. */ if (opt.def_cipher_algo && ! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr || cmd == aEncrSym || cmd == aSym || cmd == aSignSym || cmd == aSignEncrSym, opt.def_cipher_algo, GCRY_CIPHER_MODE_NONE)) log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), openpgp_cipher_algo_name (opt.def_cipher_algo), gnupg_compliance_option_string (opt.compliance)); if (opt.def_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aSignEncrSym || cmd == aSignSym || cmd == aClearsign, opt.def_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), gcry_md_algo_name (opt.def_digest_algo), gnupg_compliance_option_string (opt.compliance)); /* Fail hard. */ if (log_get_errorcount (0)) { write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL)); g10_exit (2); } /* Set the random seed file. */ if (use_random_seed) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL ); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); if (!gnupg_access (p, F_OK)) register_secured_file (p); xfree(p); } /* If there is no command but the --fingerprint is given, default to the --list-keys command. */ if (!cmd && fpr_maybe_cmd) { set_cmd (&cmd, aListKeys); } if( opt.verbose > 1 ) set_packet_list_mode(1); /* Add the keyrings, but not for some special commands. We always * need to add the keyrings if we are running under SELinux, this * is so that the rings are added to the list of secured files. * We do not add any keyring if --no-keyring or --use-keyboxd has * been used. Note that keydb_add_resource may create a new * homedir and also tries to write a common.conf to enable the use * of the keyboxd - in this case a special error code is returned * and use_keyboxd is then also set. */ if (!opt.use_keyboxd && default_keyring >= 0 && (ALWAYS_ADD_KEYRINGS || (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest))) { gpg_error_t tmperr = 0; if (!nrings || default_keyring > 0) /* Add default ring. */ tmperr = keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG, KEYDB_RESOURCE_FLAG_DEFAULT); if (gpg_err_code (tmperr) == GPG_ERR_TRUE && opt.use_keyboxd) ; /* The keyboxd has been enabled. */ else { for (sl = nrings; sl; sl = sl->next ) keydb_add_resource (sl->d, sl->flags); } } FREE_STRLIST(nrings); /* In loopback mode, never ask for the password multiple times. */ if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) { opt.passphrase_repeat = 0; } /* If no pinentry is expected shunt * gnupg_allow_set_foregound_window to avoid useless error * messages on Windows. */ if (opt.pinentry_mode != PINENTRY_MODE_ASK) { gnupg_inhibit_set_foregound_window (1); } if (cmd == aGPGConfTest) g10_exit(0); if (pwfd != -1) /* Read the passphrase now. */ read_passphrase_from_fd (pwfd); if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */ read_sessionkey_from_fd (ovrseskeyfd); fname = argc? *argv : NULL; if(fname && utf8_strings) opt.flags.utf8_filename=1; ctrl = xcalloc (1, sizeof *ctrl); gpg_init_default_ctrl (ctrl); #ifndef NO_TRUST_MODELS switch (cmd) { case aPrimegen: case aPrintMD: case aPrintMDs: case aGenRandom: case aDeArmor: case aEnArmor: case aListConfig: case aListGcryptConfig: break; case aFixTrustDB: case aExportOwnerTrust: rc = setup_trustdb (0, trustdb_name); break; case aListTrustDB: rc = setup_trustdb (argc? 1:0, trustdb_name); break; case aKeygen: case aFullKeygen: case aQuickKeygen: rc = setup_trustdb (1, trustdb_name); break; default: /* If we are using TM_ALWAYS, we do not need to create the trustdb. */ rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name); break; } if (rc) log_error (_("failed to initialize the TrustDB: %s\n"), gpg_strerror (rc)); #endif /*!NO_TRUST_MODELS*/ switch (cmd) { case aStore: case aSym: case aSign: case aSignSym: case aClearsign: if (!opt.quiet && any_explicit_recipient) log_info (_("WARNING: recipients (-r) given " "without using public key encryption\n")); break; default: break; } /* Check for certain command whether we need to migrate a secring.gpg to the gpg-agent. */ switch (cmd) { case aListSecretKeys: case aSign: case aSignEncr: case aSignEncrSym: case aSignSym: case aClearsign: case aDecrypt: case aSignKey: case aLSignKey: case aEditKey: case aPasswd: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: case aQuickKeygen: case aQuickAddUid: case aQuickAddKey: case aQuickAddADSK: case aQuickRevUid: case aQuickSetPrimaryUid: case aQuickUpdatePref: case aFullKeygen: case aKeygen: case aImport: case aExportSecret: case aExportSecretSub: case aGenRevoke: case aDesigRevoke: case aCardEdit: case aChangePIN: migrate_secring (ctrl); break; case aListKeys: if (opt.with_secret) migrate_secring (ctrl); break; default: break; } /* The command dispatcher. */ switch( cmd ) { case aServer: gpg_server (ctrl); break; case aStore: /* only store the file */ if( argc > 1 ) wrong_args("--store [filename]"); if( (rc = encrypt_store(fname)) ) { write_status_failure ("store", rc); log_error ("storing '%s' failed: %s\n", print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aSym: /* encrypt the given file only with the symmetric cipher */ if( argc > 1 ) wrong_args("--symmetric [filename]"); if( (rc = encrypt_symmetric(fname)) ) { write_status_failure ("symencrypt", rc); log_error (_("symmetric encryption of '%s' failed: %s\n"), print_fname_stdin(fname),gpg_strerror (rc) ); } break; case aEncr: /* encrypt the given file */ if(multifile) encrypt_crypt_files (ctrl, argc, argv, remusr); else { if( argc > 1 ) wrong_args("--encrypt [filename]"); if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 0, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aEncrSym: /* This works with PGP 8 in the sense that it acts just like a symmetric message. It doesn't work at all with 2 or 6. It might work with 7, but alas, I don't have a copy to test with right now. */ if( argc > 1 ) wrong_args("--symmetric --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --encrypt" " with --s2k-mode 0\n")); else if (PGP7) log_error(_("you cannot use --symmetric --encrypt" " in %s mode\n"), gnupg_compliance_option_string (opt.compliance)); else { if( (rc = encrypt_crypt (ctrl, -1, fname, remusr, 1, NULL, -1)) ) { write_status_failure ("encrypt", rc); log_error ("%s: encryption failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } } break; case aSign: /* sign the given file */ sl = NULL; if( detached_sig ) { /* sign all files */ for( ; argc; argc--, argv++ ) add_to_strlist( &sl, *argv ); } else { if( argc > 1 ) wrong_args("--sign [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } } if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL))) { write_status_failure ("sign", rc); log_error ("signing failed: %s\n", gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncr: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--sign --encrypt [filename]"); if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); break; case aSignEncrSym: /* sign and encrypt the given file */ if( argc > 1 ) wrong_args("--symmetric --sign --encrypt [filename]"); else if(opt.s2k_mode==0) log_error(_("you cannot use --symmetric --sign --encrypt" " with --s2k-mode 0\n")); else if (PGP7) log_error(_("you cannot use --symmetric --sign --encrypt" " in %s mode\n"), gnupg_compliance_option_string (opt.compliance)); else { if( argc ) { sl = xmalloc_clear( sizeof *sl + strlen(fname)); strcpy(sl->d, fname); } else sl = NULL; if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 2, remusr, NULL))) { write_status_failure ("sign-encrypt", rc); log_error("%s: symmetric+sign+encrypt failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } free_strlist(sl); } break; case aSignSym: /* sign and conventionally encrypt the given file */ if (argc > 1) wrong_args("--sign --symmetric [filename]"); rc = sign_symencrypt_file (ctrl, fname, locusr); if (rc) { write_status_failure ("sign-symencrypt", rc); log_error("%s: sign+symmetric failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aClearsign: /* make a clearsig */ if( argc > 1 ) wrong_args("--clear-sign [filename]"); if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) ) { write_status_failure ("sign", rc); log_error("%s: clear-sign failed: %s\n", print_fname_stdin(fname), gpg_strerror (rc) ); } break; case aVerify: if (multifile) { if ((rc = verify_files (ctrl, argc, argv))) log_error("verify files failed: %s\n", gpg_strerror (rc) ); } else { if ((rc = verify_signatures (ctrl, argc, argv))) log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); } if (rc) write_status_failure ("verify", rc); break; case aDecrypt: if (multifile) decrypt_messages (ctrl, argc, argv); else { if( argc > 1 ) wrong_args("--decrypt [filename]"); if( (rc = decrypt_message (ctrl, fname) )) { write_status_failure ("decrypt", rc); log_error("decrypt_message failed: %s\n", gpg_strerror (rc) ); } } break; case aQuickSignKey: case aQuickLSignKey: { const char *fpr; if (argc < 1) wrong_args ("--quick-[l]sign-key fingerprint [userids]"); fpr = *argv++; argc--; sl = NULL; for( ; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey)); free_strlist (sl); } break; case aQuickRevSig: { const char *userid, *siguserid; if (argc < 2) wrong_args ("--quick-revoke-sig USER-ID SIG-USER-ID [userids]"); userid = *argv++; argc--; siguserid = *argv++; argc--; sl = NULL; for( ; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); keyedit_quick_revsig (ctrl, userid, siguserid, sl); free_strlist (sl); } break; case aSignKey: if( argc != 1 ) wrong_args("--sign-key user-id"); /* fall through */ case aLSignKey: if( argc != 1 ) wrong_args("--lsign-key user-id"); /* fall through */ sl=NULL; if(cmd==aSignKey) append_to_strlist(&sl,"sign"); else if(cmd==aLSignKey) append_to_strlist(&sl,"lsign"); else BUG(); append_to_strlist( &sl, "save" ); username = make_username( fname ); keyedit_menu (ctrl, username, locusr, sl, 0, 0 ); xfree(username); free_strlist(sl); break; case aEditKey: /* Edit a key signature */ if( !argc ) wrong_args("--edit-key user-id [commands]"); username = make_username( fname ); if( argc > 1 ) { sl = NULL; for( argc--, argv++ ; argc; argc--, argv++ ) append_to_strlist( &sl, *argv ); keyedit_menu (ctrl, username, locusr, sl, 0, 1 ); free_strlist(sl); } else keyedit_menu (ctrl, username, locusr, NULL, 0, 1 ); xfree(username); break; case aPasswd: if (argc != 1) wrong_args("--change-passphrase "); else { username = make_username (fname); keyedit_passwd (ctrl, username); xfree (username); } break; case aDeleteKeys: case aDeleteSecretKeys: case aDeleteSecretAndPublicKeys: sl = NULL; /* Print a note if the user did not specify any key. */ if (!argc && !opt.quiet) log_info (_("Note: %s\n"), gpg_strerror (GPG_ERR_NO_KEY)); /* I'm adding these in reverse order as add_to_strlist2 reverses them again, and it's easier to understand in the proper order :) */ for( ; argc; argc-- ) add_to_strlist2( &sl, argv[argc-1], utf8_strings ); delete_keys (ctrl, sl, cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys); free_strlist(sl); break; case aCheckKeys: opt.check_sigs = 1; /* fall through */ case aListSigs: opt.list_sigs = 1; /* fall through */ case aListKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); public_key_list (ctrl, sl, 0, 0); free_strlist(sl); break; case aListSecretKeys: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); secret_key_list (ctrl, sl); free_strlist(sl); break; case aLocateKeys: case aLocateExtKeys: sl = NULL; for (; argc; argc--, argv++) add_to_strlist2( &sl, *argv, utf8_strings ); if (cmd == aLocateExtKeys && akl_empty_or_only_local ()) { /* This is a kludge to let --locate-external-keys even * work if the config file has --no-auto-key-locate. This * better matches the expectations of the user. */ release_akl (); parse_auto_key_locate (DEFAULT_AKL_LIST); } public_key_list (ctrl, sl, 1, cmd == aLocateExtKeys); free_strlist (sl); break; case aQuickKeygen: { const char *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]"); username = make_username (fname); argv++, argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } if (mopt.forbid_gen_key) gen_key_forbidden (); else quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire); xfree (username); } break; case aKeygen: /* generate a key */ if (mopt.forbid_gen_key) gen_key_forbidden (); else if( opt.batch ) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else { if (opt.command_fd != -1 && argc) { if( argc > 1 ) wrong_args("--generate-key [parameterfile]"); opt.batch = 1; generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0); } else if (argc) wrong_args ("--generate-key"); else generate_keypair (ctrl, 0, NULL, NULL, 0); } break; case aFullKeygen: /* Generate a key with all options. */ if (mopt.forbid_gen_key) gen_key_forbidden (); else if (opt.batch) { if (argc > 1) wrong_args ("--full-generate-key [parameterfile]"); generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0); } else { if (argc) wrong_args("--full-generate-key"); generate_keypair (ctrl, 1, NULL, NULL, 0); } break; case aQuickAddUid: { const char *uid, *newuid; if (argc != 2) wrong_args ("--quick-add-uid USER-ID NEW-USER-ID"); uid = *argv++; argc--; newuid = *argv++; argc--; keyedit_quick_adduid (ctrl, uid, newuid); } break; case aQuickAddKey: { const char *x_fpr, *x_algo, *x_usage, *x_expire; if (argc < 1 || argc > 4) wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]"); x_fpr = *argv++; argc--; x_algo = ""; x_usage = ""; x_expire = ""; if (argc) { x_algo = *argv++; argc--; if (argc) { x_usage = *argv++; argc--; if (argc) { x_expire = *argv++; argc--; } } } if (mopt.forbid_gen_key) gen_key_forbidden (); else keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire); } break; case aQuickAddADSK: { if (argc != 2) wrong_args ("--quick-add-adsk FINGERPRINT ADSK-FINGERPRINT"); if (mopt.forbid_gen_key) gen_key_forbidden (); else keyedit_quick_addadsk (ctrl, argv[0], argv[1]); } break; case aQuickRevUid: { const char *uid, *uidtorev; if (argc != 2) wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE"); uid = *argv++; argc--; uidtorev = *argv++; argc--; keyedit_quick_revuid (ctrl, uid, uidtorev); } break; case aQuickSetExpire: { const char *x_fpr, *x_expire; if (argc < 2) wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE [SUBKEY-FPRS]"); x_fpr = *argv++; argc--; x_expire = *argv++; argc--; keyedit_quick_set_expire (ctrl, x_fpr, x_expire, argv); } break; case aQuickSetPrimaryUid: { const char *uid, *primaryuid; if (argc != 2) wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID"); uid = *argv++; argc--; primaryuid = *argv++; argc--; keyedit_quick_set_primary (ctrl, uid, primaryuid); } break; case aQuickUpdatePref: { if (argc != 1) wrong_args ("--quick-update-pref USER-ID"); keyedit_quick_update_pref (ctrl, *argv); } break; case aFastImport: opt.import_options |= IMPORT_FAST; /* fall through */ case aImport: case aShowKeys: import_keys (ctrl, argc? argv:NULL, argc, NULL, opt.import_options, opt.key_origin, opt.key_origin_url); break; /* TODO: There are a number of command that use this same "make strlist, call function, report error, free strlist" pattern. Join them together here and avoid all that duplicated code. */ case aExport: case aSendKeys: case aRecvKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); if( cmd == aSendKeys ) rc = keyserver_export (ctrl, sl ); else if( cmd == aRecvKeys ) rc = keyserver_import (ctrl, sl ); else { export_stats_t stats = export_new_stats (); rc = export_pubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } if(rc) { if(cmd==aSendKeys) { write_status_failure ("send-keys", rc); log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc)); } else if(cmd==aRecvKeys) { write_status_failure ("recv-keys", rc); log_error (_("keyserver receive failed: %s\n"), gpg_strerror (rc)); } else { write_status_failure ("export", rc); log_error (_("key export failed: %s\n"), gpg_strerror (rc)); } } free_strlist(sl); break; case aExportSshKey: if (argc != 1) wrong_args ("--export-ssh-key "); rc = export_ssh_key (ctrl, argv[0]); if (rc) { write_status_failure ("export-ssh-key", rc); log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc)); } break; case aExportSecretSshKey: if (argc != 1) wrong_args ("--export-secret-ssh-key "); rc = export_secret_ssh_key (ctrl, argv[0]); if (rc) { write_status_failure ("export-ssh-key", rc); log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc)); } break; case aSearchKeys: sl = NULL; for (; argc; argc--, argv++) append_to_strlist2 (&sl, *argv, utf8_strings); rc = keyserver_search (ctrl, sl); if (rc) { write_status_failure ("search-keys", rc); log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc)); } free_strlist (sl); break; case aRefreshKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_refresh (ctrl, sl); if(rc) { write_status_failure ("refresh-keys", rc); log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc)); } free_strlist(sl); break; case aFetchKeys: sl = NULL; for( ; argc; argc--, argv++ ) append_to_strlist2( &sl, *argv, utf8_strings ); rc = keyserver_fetch (ctrl, sl, opt.key_origin); free_strlist (sl); if(rc) { write_status_failure ("fetch-keys", rc); log_error ("key fetch failed: %s\n",gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_NO_DATA) g10_exit (1); /* In this case return 1 and not 2. */ } break; case aExportSecret: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_seckeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aExportSecretSub: sl = NULL; for( ; argc; argc--, argv++ ) add_to_strlist2( &sl, *argv, utf8_strings ); { export_stats_t stats = export_new_stats (); export_secsubkeys (ctrl, sl, opt.export_options, stats); export_print_stats (stats); export_release_stats (stats); } free_strlist(sl); break; case aGenRevoke: if( argc != 1 ) wrong_args("--generate-revocation user-id"); username = make_username(*argv); gen_revoke (ctrl, username ); xfree( username ); break; case aDesigRevoke: if (argc != 1) wrong_args ("--generate-designated-revocation user-id"); username = make_username (*argv); gen_desig_revoke (ctrl, username, locusr); xfree (username); break; case aDeArmor: if( argc > 1 ) wrong_args("--dearmor [file]"); rc = dearmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("dearmor", rc); log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc)); } break; case aEnArmor: if( argc > 1 ) wrong_args("--enarmor [file]"); rc = enarmor_file( argc? *argv: NULL ); if( rc ) { write_status_failure ("enarmor", rc); log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc)); } break; case aPrimegen: #if 0 /*FIXME*/ { int mode = argc < 2 ? 0 : atoi(*argv); if( mode == 1 && argc == 2 ) { mpi_print (es_stdout, generate_public_prime( atoi(argv[1]) ), 1); } else if( mode == 2 && argc == 3 ) { mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), NULL,NULL ), 1); } else if( mode == 3 && argc == 3 ) { MPI *factors; mpi_print (es_stdout, generate_elg_prime( 1, atoi(argv[1]), atoi(argv[2]), NULL,&factors ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, factors[0], 1 ); /* print q */ } else if( mode == 4 && argc == 3 ) { MPI g = mpi_alloc(1); mpi_print (es_stdout, generate_elg_prime( 0, atoi(argv[1]), atoi(argv[2]), g, NULL ), 1); es_putc ('\n', es_stdout); mpi_print (es_stdout, g, 1 ); mpi_free (g); } else wrong_args("--gen-prime mode bits [qbits] "); es_putc ('\n', es_stdout); } #endif wrong_args("--gen-prime not yet supported "); break; case aGenRandom: { int level = argc ? atoi(*argv):0; int count = argc > 1 ? atoi(argv[1]): 0; int endless = !count; int hexhack = (level == 16); if (hexhack) level = 1; /* Level 30 uses the same algorithm as our magic wand in * pinentry/gpg-agent. */ if (level == 30) { unsigned int nbits = 150; size_t nbytes = (nbits + 7) / 8; void *rand; char *generated; rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM); if (!rand) log_fatal ("failed to generate random password\n"); generated = zb32_encode (rand, nbits); gcry_free (rand); es_fputs (generated, es_stdout); es_putc ('\n', es_stdout); xfree (generated); break; } if (argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0) wrong_args ("--gen-random 0|1|2|16|30 [count]"); while (endless || count) { byte *p; /* We need a multiple of 3, so that in case of armored * output we get a correct string. No linefolding is * done, as it is best to leave this to other tools */ size_t n = !endless && count < 99? count : 99; size_t nn; p = gcry_random_bytes (n, level); if (hexhack) { for (nn = 0; nn < n; nn++) es_fprintf (es_stdout, "%02x", p[nn]); } else if (opt.armor) { char *tmp = make_radix64_string (p, n); es_fputs (tmp, es_stdout); xfree (tmp); if (n%3 == 1) es_putc ('=', es_stdout); if (n%3) es_putc ('=', es_stdout); } else { es_set_binary (es_stdout); es_fwrite( p, n, 1, es_stdout ); } xfree(p); if (!endless) count -= n; } if (opt.armor || hexhack) es_putc ('\n', es_stdout); } break; case aPrintMD: if( argc < 1) wrong_args("--print-md algo [files]"); { int all_algos = (**argv=='*' && !(*argv)[1]); int algo = all_algos? 0 : gcry_md_map_name (*argv); if( !algo && !all_algos ) log_error(_("invalid hash algorithm '%s'\n"), *argv ); else { argc--; argv++; if( !argc ) print_mds(NULL, algo); else { for(; argc; argc--, argv++ ) print_mds(*argv, algo); } } } break; case aPrintMDs: /* old option */ if( !argc ) print_mds(NULL,0); else { for(; argc; argc--, argv++ ) print_mds(*argv,0); } break; #ifndef NO_TRUST_MODELS case aListTrustDB: if( !argc ) list_trustdb (ctrl, es_stdout, NULL); else { for( ; argc; argc--, argv++ ) list_trustdb (ctrl, es_stdout, *argv ); } break; case aUpdateTrustDB: if( argc ) wrong_args("--update-trustdb"); update_trustdb (ctrl); break; case aCheckTrustDB: /* Old versions allowed for arguments - ignore them */ check_trustdb (ctrl); break; case aFixTrustDB: how_to_fix_the_trustdb (); break; case aListTrustPath: if( !argc ) wrong_args("--list-trust-path "); for( ; argc; argc--, argv++ ) { username = make_username( *argv ); list_trust_path( username ); xfree(username); } break; case aExportOwnerTrust: if( argc ) wrong_args("--export-ownertrust"); export_ownertrust (ctrl); break; case aImportOwnerTrust: if( argc > 1 ) wrong_args("--import-ownertrust [file]"); import_ownertrust (ctrl, argc? *argv:NULL ); break; #endif /*!NO_TRUST_MODELS*/ case aRebuildKeydbCaches: if (argc) wrong_args ("--rebuild-keydb-caches"); keydb_rebuild_caches (ctrl, 1); break; #ifdef ENABLE_CARD_SUPPORT case aCardStatus: if (argc == 0) card_status (ctrl, es_stdout, NULL); else if (argc == 1) card_status (ctrl, es_stdout, *argv); else wrong_args ("--card-status [serialno]"); break; case aCardEdit: if (argc) { sl = NULL; for (argc--, argv++ ; argc; argc--, argv++) append_to_strlist (&sl, *argv); card_edit (ctrl, sl); free_strlist (sl); } else card_edit (ctrl, NULL); break; case aChangePIN: if (!argc) change_pin (0,1); else if (argc == 1) change_pin (atoi (*argv),1); else wrong_args ("--change-pin [no]"); break; #endif /* ENABLE_CARD_SUPPORT*/ case aListConfig: { char *str=collapse_args(argc,argv); list_config(str); xfree(str); } break; case aListGcryptConfig: /* Fixme: It would be nice to integrate that with --list-config but unfortunately there is no way yet to have libgcrypt print it to an estream for further parsing. */ gcry_control (GCRYCTL_PRINT_CONFIG, stdout); break; case aTOFUPolicy: #ifdef USE_TOFU { int policy; int i; KEYDB_HANDLE hd; if (argc < 2) wrong_args ("--tofu-policy POLICY KEYID [KEYID...]"); policy = parse_tofu_policy (argv[0]); hd = keydb_new (ctrl); if (! hd) { write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL)); g10_exit (1); } tofu_begin_batch_update (ctrl); for (i = 1; i < argc; i ++) { KEYDB_SEARCH_DESC desc; kbnode_t kb; rc = classify_user_id (argv[i], &desc, 0); if (rc) { log_error (_("error parsing key specification '%s': %s\n"), argv[i], gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID || desc.mode == KEYDB_SEARCH_MODE_LONG_KID || desc.mode == KEYDB_SEARCH_MODE_FPR || desc.mode == KEYDB_SEARCH_MODE_KEYGRIP)) { log_error (_("'%s' does not appear to be a valid" " key ID, fingerprint or keygrip\n"), argv[i]); write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL)); g10_exit (1); } rc = keydb_search_reset (hd); if (rc) { /* This should not happen, thus no need to tranalate the string. */ log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } rc = keydb_search (hd, &desc, 1, NULL); if (rc) { log_error (_("key \"%s\" not found: %s\n"), argv[i], gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } rc = keydb_get_keyblock (hd, &kb); if (rc) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (rc)); write_status_failure ("tofu-driver", rc); g10_exit (1); } merge_keys_and_selfsig (ctrl, kb); if (tofu_set_policy (ctrl, kb, policy)) { write_status_failure ("tofu-driver", rc); g10_exit (1); } release_kbnode (kb); } tofu_end_batch_update (ctrl); keydb_release (hd); } #endif /*USE_TOFU*/ break; default: if (!opt.quiet) log_info (_("WARNING: no command supplied." " Trying to guess what you mean ...\n")); /*FALLTHRU*/ case aListPackets: if( argc > 1 ) wrong_args("[filename]"); /* Issue some output for the unix newbie */ if (!fname && !opt.outfile && gnupg_isatty (fileno (stdin)) && gnupg_isatty (fileno (stdout)) && gnupg_isatty (fileno (stderr))) log_info(_("Go ahead and type your message ...\n")); a = iobuf_open(fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if( !a ) log_error(_("can't open '%s'\n"), print_fname_stdin(fname)); else { if( !opt.no_armor ) { if( use_armor_filter( a ) ) { afx = new_armor_context (); push_armor_filter (afx, a); } } if( cmd == aListPackets ) { opt.list_packets=1; set_packet_list_mode(1); } rc = proc_packets (ctrl, NULL, a ); if( rc ) { write_status_failure ("-", rc); log_error ("processing message failed: %s\n", gpg_strerror (rc)); } iobuf_close(a); } break; } /* cleanup */ gpg_deinit_default_ctrl (ctrl); xfree (ctrl); release_armor_context (afx); FREE_STRLIST(remusr); FREE_STRLIST(locusr); g10_exit(0); return 8; /*NEVER REACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void g10_exit( int rc ) { /* If we had an error but not printed an error message, do it now. * Note that write_status_failure will never print a second failure * status line. */ if (rc) write_status_failure ("gpg-exit", gpg_error (GPG_ERR_GENERAL)); gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (DBG_CLOCK) log_clock ("stop"); if ( (opt.debug & DBG_MEMSTAT_VALUE) ) { keydb_dump_stats (); sig_check_dump_stats (); objcache_dump_stats (); gcry_control (GCRYCTL_DUMP_MEMORY_STATS); gcry_control (GCRYCTL_DUMP_RANDOM_STATS); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); gnupg_block_all_signals (); emergency_cleanup (); if (rc) ; else if (log_get_errorcount(0)) rc = 2; else if (g10_errors_seen) rc = 1; else if (opt.assert_signer_list && !assert_signer_true) rc = 1; exit (rc); } /* Pretty-print hex hashes. This assumes at least an 80-character display, but there are a few other similar assumptions in the display code. */ static void print_hex (gcry_md_hd_t md, int algo, const char *fname) { int i,n,count,indent=0; const byte *p; if (fname) indent = es_printf("%s: ",fname); if (indent>40) { es_printf ("\n"); indent=0; } if (algo==DIGEST_ALGO_RMD160) indent += es_printf("RMD160 = "); else if (algo>0) indent += es_printf("%6s = ", gcry_md_algo_name (algo)); else algo = abs(algo); count = indent; p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); count += es_printf ("%02X",*p++); for(i=1;i79) { es_printf ("\n%*s",indent,indent?" ":""); count = indent; } else count += es_printf(" "); if (!(i%8)) count += es_printf(" "); } else if (n==20) { if(!(i%2)) { if(count+4>79) { es_printf ("\n%*s",indent,indent?" ":""); count=indent; } else count += es_printf(" "); } if (!(i%10)) count += es_printf(" "); } else { if(!(i%4)) { if (count+8>=79) { es_printf ("\n%*s",indent, indent?" ":""); count=indent; } else count += es_printf(" "); } } count += es_printf("%02X",*p); } es_printf ("\n"); } static void print_hashline( gcry_md_hd_t md, int algo, const char *fname ) { int i, n; const byte *p; if ( fname ) { for (p = fname; *p; p++ ) { if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' ) es_printf ("%%%02X", *p ); else es_putc (*p, es_stdout); } } es_putc (':', es_stdout); es_printf ("%d:", algo); p = gcry_md_read (md, algo); n = gcry_md_get_algo_dlen (algo); for(i=0; i < n ; i++, p++ ) es_printf ("%02X", *p); es_fputs (":\n", es_stdout); } static void print_mds( const char *fname, int algo ) { estream_t fp; char buf[1024]; size_t n; gcry_md_hd_t md; if (!fname) { fp = es_stdin; es_set_binary (fp); } else { fp = es_fopen (fname, "rb" ); if (fp && is_secured_file (es_fileno (fp))) { es_fclose (fp); fp = NULL; gpg_err_set_errno (EPERM); } } if (!fp) { log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) ); return; } gcry_md_open (&md, 0, 0); if (algo) gcry_md_enable (md, algo); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) gcry_md_enable (md, GCRY_MD_MD5); gcry_md_enable (md, GCRY_MD_SHA1); if (!gcry_md_test_algo (GCRY_MD_RMD160)) gcry_md_enable (md, GCRY_MD_RMD160); if (!gcry_md_test_algo (GCRY_MD_SHA224)) gcry_md_enable (md, GCRY_MD_SHA224); if (!gcry_md_test_algo (GCRY_MD_SHA256)) gcry_md_enable (md, GCRY_MD_SHA256); if (!gcry_md_test_algo (GCRY_MD_SHA384)) gcry_md_enable (md, GCRY_MD_SHA384); if (!gcry_md_test_algo (GCRY_MD_SHA512)) gcry_md_enable (md, GCRY_MD_SHA512); } while ((n=es_fread (buf, 1, DIM(buf), fp))) gcry_md_write (md, buf, n); if (es_ferror(fp)) log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno)); else { gcry_md_final (md); if (opt.with_colons) { if ( algo ) print_hashline (md, algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hashline( md, GCRY_MD_MD5, fname ); print_hashline( md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hashline( md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hashline (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hashline( md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hashline ( md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hashline ( md, GCRY_MD_SHA512, fname ); } } else { if (algo) print_hex (md, -algo, fname); else { if (!gcry_md_test_algo (GCRY_MD_MD5)) print_hex (md, GCRY_MD_MD5, fname); print_hex (md, GCRY_MD_SHA1, fname ); if (!gcry_md_test_algo (GCRY_MD_RMD160)) print_hex (md, GCRY_MD_RMD160, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA224)) print_hex (md, GCRY_MD_SHA224, fname); if (!gcry_md_test_algo (GCRY_MD_SHA256)) print_hex (md, GCRY_MD_SHA256, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA384)) print_hex (md, GCRY_MD_SHA384, fname ); if (!gcry_md_test_algo (GCRY_MD_SHA512)) print_hex (md, GCRY_MD_SHA512, fname ); } } } gcry_md_close (md); if (fp != es_stdin) es_fclose (fp); } /**************** * Check the supplied name,value string and add it to the notation * data to be used for signatures. which==0 for sig notations, and 1 * for cert notations. */ static void add_notation_data( const char *string, int which ) { struct notation *notation; notation=string_to_notation(string,utf8_strings); if(notation) { if(which) { notation->next=opt.cert_notations; opt.cert_notations=notation; } else { notation->next=opt.sig_notations; opt.sig_notations=notation; } } } static void add_policy_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } static void add_keyserver_url( const char *string, int which ) { unsigned int i,critical=0; strlist_t sl; if(*string=='!') { string++; critical=1; } for(i=0;iflags |= 1; } static void read_sessionkey_from_fd (int fd) { int i, len; char *line; if (! gnupg_fd_valid (fd)) log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno)); for (line = NULL, i = len = 100; ; i++ ) { if (i >= len-1 ) { char *tmp = line; len += 100; line = xmalloc_secure (len); if (tmp) { memcpy (line, tmp, i); xfree (tmp); } else i=0; } if (read (fd, line + i, 1) != 1 || line[i] == '\n') break; } line[i] = 0; log_debug ("seskey: %s\n", line); gpgrt_annotate_leaked_object (line); opt.override_session_key = line; } diff --git a/g10/gpgv.c b/g10/gpgv.c index f2895563e..c46cfa9b7 100644 --- a/g10/gpgv.c +++ b/g10/gpgv.c @@ -1,822 +1,822 @@ /* gpgv.c - The GnuPG signature verify utility * Copyright (C) 1998-2020 Free Software Foundation, Inc. * Copyright (C) 1997-2019 Werner Koch * Copyright (C) 2015-2020 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_DOSISH_SYSTEM #include /* for setmode() */ #endif #ifdef HAVE_LIBREADLINE #define GNUPG_LIBREADLINE_H_INCLUDED #include #endif #define INCLUDED_BY_MAIN_MODULE 1 #include "gpg.h" #include "../common/util.h" #include "packet.h" #include "../common/iobuf.h" #include "main.h" #include "options.h" #include "keydb.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/status.h" #include "call-agent.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oBatch = 500, oKeyring, oIgnoreTimeConflict, oStatusFD, oLoggerFD, oLoggerFile, oHomedir, oWeakDigest, oEnableSpecialFilenames, oDebug, aTest }; static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oKeyring, "keyring", N_("|FILE|take the keys from the keyring FILE")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", N_("make timestamp conflicts only a warning")), - ARGPARSE_s_i (oStatusFD, "status-fd", + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), - ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_s (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_s (oLoggerFile, "log-file", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oWeakDigest, "weak-digest", N_("|ALGO|reject signatures made with ALGO")), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_PACKET_VALUE , "packet" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_FILTER_VALUE , "filter" }, { DBG_IOBUF_VALUE , "iobuf" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_TRUST_VALUE , "trust" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; int g10_errors_seen = 0; int assert_signer_true = 0; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static const char * my_strusage( int level ) { static char *ver_gcry; const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPG@v (GnuPG)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpgv [options] [files] (-h for help)"); break; case 41: p = _("Syntax: gpgv [options] [files]\n" "Check signatures against known trusted keys\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; default: p = NULL; } return p; } int main( int argc, char **argv ) { gpgrt_argparse_t pargs; int rc=0; strlist_t sl; strlist_t nrings = NULL; ctrl_t ctrl; early_system_init (); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpgv", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gcry_control (GCRYCTL_DISABLE_SECMEM, 0); gnupg_init_signals (0, NULL); opt.command_fd = -1; /* no command fd */ opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE; opt.trust_model = TM_ALWAYS; opt.no_sig_cache = 1; opt.flags.require_cross_cert = 1; opt.batch = 1; opt.answer_yes = 1; opt.weak_digests = NULL; tty_no_terminal(1); tty_batchmode(1); dotlock_disable (); gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); additional_weak_digest("MD5"); gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG); pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparser (&pargs, opts, NULL)) { switch (pargs.r_opt) { case ARGPARSE_CONFFILE: break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; opt.list_sigs=1; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oStatusFD: - set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + set_status_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oLoggerFD: - log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + log_set_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oLoggerFile: log_set_file (pargs.r.ret_str); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID) ); break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oWeakDigest: additional_weak_digest(pargs.r.ret_str); break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oEnableSpecialFilenames: enable_special_filenames (); break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) g10_exit(2); if (opt.verbose > 1) set_packet_list_mode(1); /* Note: We open all keyrings in read-only mode. */ if (!nrings) /* No keyring given: use default one. */ keydb_add_resource ("trustedkeys" EXTSEP_S "kbx", (KEYDB_RESOURCE_FLAG_READONLY |KEYDB_RESOURCE_FLAG_GPGVDEF)); for (sl = nrings; sl; sl = sl->next) keydb_add_resource (sl->d, KEYDB_RESOURCE_FLAG_READONLY); FREE_STRLIST (nrings); ctrl = xcalloc (1, sizeof *ctrl); if ((rc = verify_signatures (ctrl, argc, argv))) log_error("verify signatures failed: %s\n", gpg_strerror (rc) ); keydb_release (ctrl->cached_getkey_kdb); xfree (ctrl); /* cleanup */ g10_exit (0); return 8; /*NOTREACHED*/ } void g10_exit( int rc ) { rc = rc? rc : log_get_errorcount(0)? 2 : g10_errors_seen? 1 : 0; exit(rc ); } /* Stub: * We have to override the trustcheck from pkclist.c because * this utility assumes that all keys in the keyring are trustworthy */ gpg_error_t check_signatures_trust (ctrl_t ctrl, kbnode_t kblock, PKT_public_key *pk, PKT_signature *sig) { (void)ctrl; (void)kblock; (void)pk; (void)sig; return 0; } void read_trust_options (ctrl_t ctrl, byte *trust_model, ulong *created, ulong *nextcheck, byte *marginals, byte *completes, byte *cert_depth, byte *min_cert_level) { (void)ctrl; (void)trust_model; (void)created; (void)nextcheck; (void)marginals; (void)completes; (void)cert_depth; (void)min_cert_level; } /* Stub: * We don't have the trustdb , so we have to provide some stub functions * instead */ int cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return 0; } void check_trustdb_stale (ctrl_t ctrl) { (void)ctrl; } int get_validity_info (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid) { (void)ctrl; (void)kb; (void)pk; (void)uid; return '?'; } unsigned int get_validity (ctrl_t ctrl, kbnode_t kb, PKT_public_key *pk, PKT_user_id *uid, PKT_signature *sig, int may_ask) { (void)ctrl; (void)kb; (void)pk; (void)uid; (void)sig; (void)may_ask; return 0; } const char * trust_value_to_string (unsigned int value) { (void)value; return "err"; } const char * uid_trust_string_fixed (ctrl_t ctrl, PKT_public_key *key, PKT_user_id *uid) { (void)ctrl; (void)key; (void)uid; return "err"; } int get_ownertrust_info (ctrl_t ctrl, PKT_public_key *pk, int no_create) { (void)ctrl; (void)pk; (void)no_create; return '?'; } unsigned int get_ownertrust (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return TRUST_UNKNOWN; } /* Stubs: * Because we only work with trusted keys, it does not make sense to * get them from a keyserver */ struct keyserver_spec * keyserver_match (struct keyserver_spec *spec) { (void)spec; return NULL; } int keyserver_any_configured (ctrl_t ctrl) { (void)ctrl; return 0; } int keyserver_import_keyid (u32 *keyid, void *dummy, unsigned int flags) { (void)keyid; (void)dummy; (void)flags; return -1; } int keyserver_import_fprint (ctrl_t ctrl, const byte *fprint,size_t fprint_len, struct keyserver_spec *keyserver, unsigned int flags) { (void)ctrl; (void)fprint; (void)fprint_len; (void)keyserver; (void)flags; return -1; } int keyserver_import_fprint_ntds (ctrl_t ctrl, const byte *fprint, size_t fprint_len) { (void)ctrl; (void)fprint; (void)fprint_len; return -1; } int keyserver_import_cert (const char *name) { (void)name; return -1; } gpg_error_t keyserver_import_wkd (ctrl_t ctrl, const char *name, unsigned int flags, unsigned char **fpr, size_t *fpr_len) { (void)ctrl; (void)name; (void)flags; (void)fpr; (void)fpr_len; return GPG_ERR_BUG; } int keyserver_import_mbox (const char *name,struct keyserver_spec *spec) { (void)name; (void)spec; return -1; } int keyserver_import_ntds (ctrl_t ctrl, const char *mbox, unsigned char **fpr, size_t *fprlen) { (void)ctrl; (void)mbox; (void)fpr; (void)fprlen; return -1; } int keyserver_import_ldap (const char *name) { (void)name; return -1; } gpg_error_t read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname, const void *buffer, size_t buflen, kbnode_t *r_keyblock) { (void)ctrl; (void)fname; (void)buffer; (void)buflen; (void)r_keyblock; return -1; } gpg_error_t import_included_key_block (ctrl_t ctrl, kbnode_t keyblock) { (void)ctrl; (void)keyblock; return -1; } /* Stub: * No encryption here but mainproc links to these functions. */ gpg_error_t get_session_key (ctrl_t ctrl, struct pubkey_enc_list *k, DEK *dek) { (void)ctrl; (void)k; (void)dek; return GPG_ERR_GENERAL; } /* Stub: */ gpg_error_t get_override_session_key (DEK *dek, const char *string) { (void)dek; (void)string; return GPG_ERR_GENERAL; } /* Stub: */ int decrypt_data (ctrl_t ctrl, void *procctx, PKT_encrypted *ed, DEK *dek, int *compliance_error) { (void)ctrl; (void)procctx; (void)ed; (void)dek; (void)compliance_error; return GPG_ERR_GENERAL; } /* Stub: * No interactive commands, so we don't need the helptexts */ void display_online_help (const char *keyword) { (void)keyword; } /* Stub: * We don't use secret keys, but getkey.c links to this */ int check_secret_key (PKT_public_key *pk, int n) { (void)pk; (void)n; return GPG_ERR_GENERAL; } /* Stub: * No secret key, so no passphrase needed */ DEK * passphrase_to_dek (int cipher_algo, STRING2KEY *s2k, int create, int nocache, const char *tmp, unsigned int flags, int *canceled) { (void)cipher_algo; (void)s2k; (void)create; (void)nocache; (void)tmp; (void)flags; if (canceled) *canceled = 0; return NULL; } void passphrase_clear_cache (const char *cacheid) { (void)cacheid; } struct keyserver_spec * parse_preferred_keyserver(PKT_signature *sig) { (void)sig; return NULL; } struct keyserver_spec * parse_keyserver_uri (const char *uri, int require_scheme, const char *configname, unsigned int configlineno) { (void)uri; (void)require_scheme; (void)configname; (void)configlineno; return NULL; } void free_keyserver_spec (struct keyserver_spec *keyserver) { (void)keyserver; } /* Stubs to avoid linking to photoid.c */ void show_photos (const struct user_attribute *attrs, int count, PKT_public_key *pk) { (void)attrs; (void)count; (void)pk; } int parse_image_header (const struct user_attribute *attr, byte *type, u32 *len) { (void)attr; (void)type; (void)len; return 0; } char * image_type_to_string (byte type, int string) { (void)type; (void)string; return NULL; } #ifdef ENABLE_CARD_SUPPORT int agent_scd_getattr (const char *name, struct agent_card_info_s *info) { (void)name; (void)info; return 0; } #endif /* ENABLE_CARD_SUPPORT */ /* We do not do any locking, so use these stubs here */ void dotlock_disable (void) { } dotlock_t dotlock_create (const char *file_to_lock, unsigned int flags) { (void)file_to_lock; (void)flags; return NULL; } void dotlock_destroy (dotlock_t h) { (void)h; } int dotlock_take (dotlock_t h, long timeout) { (void)h; (void)timeout; return 0; } int dotlock_release (dotlock_t h) { (void)h; return 0; } void dotlock_remove_lockfiles (void) { } int agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) { (void)ctrl; (void)pk; return 0; } gpg_error_t agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) { (void)ctrl; (void)keyblock; return gpg_error (GPG_ERR_NO_SECKEY); } gpg_error_t agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno, int *r_cleartext) { (void)ctrl; (void)hexkeygrip; (void)r_cleartext; *r_serialno = NULL; return gpg_error (GPG_ERR_NO_SECKEY); } gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, const void *prefix, size_t prefixlen, export_stats_t stats, kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) { (void)ctrl; (void)keyspec; (void)options; (void)prefix; (void)prefixlen; (void)stats; *r_keyblock = NULL; *r_data = NULL; *r_datalen = 0; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } gpg_error_t tofu_write_tfs_record (ctrl_t ctrl, estream_t fp, PKT_public_key *pk, const char *user_id) { (void)ctrl; (void)fp; (void)pk; (void)user_id; return gpg_error (GPG_ERR_GENERAL); } gpg_error_t tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id, enum tofu_policy *policy) { (void)ctrl; (void)pk; (void)user_id; (void)policy; return gpg_error (GPG_ERR_GENERAL); } const char * tofu_policy_str (enum tofu_policy policy) { (void)policy; return "unknown"; } void tofu_begin_batch_update (ctrl_t ctrl) { (void)ctrl; } void tofu_end_batch_update (ctrl_t ctrl) { (void)ctrl; } gpg_error_t tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb) { (void) ctrl; (void) kb; return 0; } int get_revocation_reason (PKT_signature *sig, char **r_reason, char **r_comment, size_t *r_commentlen) { (void)sig; (void)r_commentlen; if (r_reason) *r_reason = NULL; if (r_comment) *r_comment = NULL; return 0; } const char * impex_filter_getval (void *cookie, const char *propname) { (void)cookie; (void)propname; return NULL; } diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 07c3ff480..ceb58a13f 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1,2412 +1,2412 @@ /* gpgsm.c - GnuPG for S/MIME * Copyright (C) 2001-2020 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2021 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include /*#include */ #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgsm.h" #include #include /* malloc hooks */ #include "passphrase.h" #include "../common/shareddefs.h" #include "../kbx/keybox.h" /* malloc hooks */ #include "../common/i18n.h" #include "keydb.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "../common/init.h" #include "../common/compliance.h" #include "../common/comopt.h" #include "minip12.h" #ifndef O_BINARY #define O_BINARY 0 #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', aListKeys = 'k', aListSecretKeys = 'K', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', aSign = 's', oUser = 'u', oVerbose = 'v', oBatch = 500, aClearsign, aKeygen, aSignEncr, aDeleteKey, aImport, aVerify, aListExternalKeys, aListChain, aSendKeys, aRecvKeys, aExport, aExportSecretKeyP12, aExportSecretKeyP8, aExportSecretKeyRaw, aServer, aLearnCard, aCallDirmngr, aCallProtectTool, aPasswd, aGPGConfList, aGPGConfTest, aDumpKeys, aDumpChain, aDumpSecretKeys, aDumpExternalKeys, aShowCerts, aKeydbClearSomeCertFlags, aFingerprint, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugNone, oDebugWait, oDebugAllowCoreDump, oDebugNoChainValidation, oDebugIgnoreExpiration, oDebugForceECDHSHA1KDF, oLogFile, oNoLogFile, oAuditLog, oHtmlAuditLog, oLogTime, oEnableSpecialFilenames, oAgentProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oPreferSystemDirmngr, oDirmngrProgram, oDisableDirmngr, oProtectToolProgram, oFakedSystemTime, oPassphraseFD, oPinentryMode, oRequestOrigin, oAssumeArmor, oAssumeBase64, oAssumeBinary, oBase64, oNoArmor, oP12Charset, oCompliance, oDisableCRLChecks, oEnableCRLChecks, oDisableTrustedCertCRLCheck, oEnableTrustedCertCRLCheck, oForceCRLRefresh, oEnableIssuerBasedCRLCheck, oDisableOCSP, oEnableOCSP, oIncludeCerts, oPolicyFile, oDisablePolicyChecks, oEnablePolicyChecks, oAutoIssuerKeyRetrieve, oMinRSALength, oWithFingerprint, oWithMD5Fingerprint, oWithKeygrip, oWithSecret, oWithKeyScreening, oAnswerYes, oAnswerNo, oNoPrettyDN, oKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oStatusFD, oCipherAlgo, oDigestAlgo, oExtraDigestAlgo, oNoVerbose, oNoSecmemWarn, oNoDefKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oWithColons, oWithKeyData, oWithValidation, oWithEphemeralKeys, oSkipVerify, oValidationModel, oKeyServer, oKeyServer_deprecated, oEncryptTo, oNoEncryptTo, oLoggerFD, oDisableCipherAlgo, oDisablePubkeyAlgo, oIgnoreTimeConflict, oNoRandomSeedFile, oNoCommonCertsImport, oIgnoreCertExtension, oIgnoreCertWithOID, oAuthenticode, oAttribute, oChUid, oUseKeyboxd, oKeyboxdProgram, oRequireCompliance, oCompatibilityFlags, oKbxBufferSize, oNoAutostart }; static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aSign, "sign", N_("make a signature")), /*ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ),*/ ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), /*ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),*/ ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), ARGPARSE_c (aVerify, "verify", N_("verify a signature")), ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), ARGPARSE_c (aListExternalKeys, "list-external-keys", N_("list external keys")), ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")), ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")), ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")), ARGPARSE_c (aKeygen, "gen-key", "@"), ARGPARSE_c (aDeleteKey, "delete-keys", N_("remove keys from the public keyring")), /*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/ /*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/ ARGPARSE_c (aImport, "import", N_("import certificates")), ARGPARSE_c (aExport, "export", N_("export certificates")), /* We use -raw and not -p1 for pkcs#1 secret key export so that it won't accidentally be used in case -p12 was intended. */ ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"), ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"), ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"), ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aCallDirmngr, "call-dirmngr", N_("pass a command to the dirmngr")), ARGPARSE_c (aCallProtectTool, "call-protect-tool", N_("invoke gpg-protect-tool")), ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), ARGPARSE_c (aPasswd, "passwd", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aShowCerts, "show-certs", "@"), ARGPARSE_c (aDumpKeys, "dump-cert", "@"), ARGPARSE_c (aDumpKeys, "dump-keys", "@"), ARGPARSE_c (aDumpChain, "dump-chain", "@"), ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"), ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"), ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugNone, "debug-none", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"), ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"), ARGPARSE_s_n (oDebugForceECDHSHA1KDF, "debug-force-ecdh-sha1kdf", "@"), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), - ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), + ARGPARSE_s_s (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_n (oLogTime, "log-time", "@"), ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"), ARGPARSE_s_s (oValidationModel, "validation-model", "@"), ARGPARSE_s_i (oIncludeCerts, "include-certs", N_("|N|number of certificates to include") ), ARGPARSE_s_s (oPolicyFile, "policy-file", N_("|FILE|take policy information from FILE")), ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"), ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"), ARGPARSE_s_s (oIgnoreCertWithOID, "ignore-cert-with-oid", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), ARGPARSE_s_n (oAssumeArmor, "assume-armor", N_("assume input is in PEM format")), ARGPARSE_s_n (oAssumeBase64, "assume-base64", N_("assume input is in base-64 format")), ARGPARSE_s_n (oAssumeBinary, "assume-binary", N_("assume input is in binary format")), ARGPARSE_header ("Output", N_("Options controlling the output")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_n (oArmor, "armour", "@"), ARGPARSE_s_n (oNoArmor, "no-armor", "@"), ARGPARSE_s_n (oNoArmor, "no-armour", "@"), ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oAuthenticode, "authenticode", "@"), ARGPARSE_s_s (oAttribute, "attribute", "@"), ARGPARSE_header (NULL, N_("Options to specify keys")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oDefaultKey, "default-key", N_("|USER-ID|use USER-ID as default secret key")), ARGPARSE_s_s (oEncryptTo, "encrypt-to", N_("|NAME|encrypt to user ID NAME as well")), ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), /* Not yet used: */ /* ARGPARSE_s_s (oDefRecipient, "default-recipient", */ /* N_("|NAME|use NAME as default recipient")), */ /* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */ /* N_("use the default key as default recipient")), */ /* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */ ARGPARSE_s_s (oKeyring, "keyring", N_("|FILE|add keyring to the list of keyrings")), ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), ARGPARSE_s_s (oKeyServer_deprecated, "ldapserver", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"), ARGPARSE_header ("ImportExport", N_("Options controlling key import and export")), ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", N_("disable all access to the dirmngr")), ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", N_("fetch missing issuer certificates")), ARGPARSE_s_s (oP12Charset, "p12-charset", N_("|NAME|use encoding NAME for PKCS#12 passphrases")), ARGPARSE_header ("Keylist", N_("Options controlling key listings")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithValidation, "with-validation", "@"), ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"), ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"), ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"), ARGPARSE_s_n (oNoPrettyDN, "no-pretty-dn", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks", N_("never consult a CRL")), ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"), ARGPARSE_s_n (oDisableTrustedCertCRLCheck, "disable-trusted-cert-crl-check", N_("do not check CRLs for root certificates")), ARGPARSE_s_n (oEnableTrustedCertCRLCheck, "enable-trusted-cert-crl-check", "@"), ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"), ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")), ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks", N_("do not check certificate policies")), ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"), ARGPARSE_s_s (oCipherAlgo, "cipher-algo", N_("|NAME|use cipher algorithm NAME")), ARGPARSE_s_s (oDigestAlgo, "digest-algo", N_("|NAME|use message digest algorithm NAME")), ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"), ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), ARGPARSE_header (NULL, N_("Options for unattended use")), ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")), ARGPARSE_s_n (oNoBatch, "no-batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")), ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), - ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), + ARGPARSE_s_s (oPassphraseFD, "passphrase-fd", "@"), ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), ARGPARSE_header (NULL, N_("Other options")), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"), ARGPARSE_s_n (oEnableIssuerBasedCRLCheck, "enable-issuer-based-crl-check", "@"), ARGPARSE_s_s (oAuditLog, "audit-log", N_("|FILE|write an audit log to FILE")), ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"), 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_s (oChUid, "chuid", "@"), ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"), ARGPARSE_p_u (oKbxBufferSize, "kbx-buffer-size", "@"), ARGPARSE_header (NULL, ""), /* Stop the header group. */ /* Command aliases. */ ARGPARSE_c (aListKeys, "list-key", "@"), ARGPARSE_c (aListChain, "list-signatures", "@"), ARGPARSE_c (aListChain, "list-sigs", "@"), ARGPARSE_c (aListChain, "check-signatures", "@"), ARGPARSE_c (aListChain, "check-sigs", "@"), ARGPARSE_c (aDeleteKey, "delete-key", "@"), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_X509_VALUE , "x509" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { 0, NULL } }; /* The list of compatibility flags. */ static struct compatibility_flags_s compatibility_flags [] = { { COMPAT_ALLOW_KA_TO_ENCR, "allow-ka-to-encr" }, { 0, NULL } }; /* Global variable to keep an error count. */ int gpgsm_errors_seen = 0; /* It is possible that we are currentlu running under setuid permissions */ static int maybe_setuid = 1; /* Helper to implement --debug-level and --debug*/ static const char *debug_level; static unsigned int debug_value; /* Helper for --log-time; */ static int opt_log_time; /* Default value for include-certs. We need an extra macro for gpgconf-list because the variable will be changed by the command line option. It is often cumbersome to locate intermediate certificates, thus by default we include all certificates in the chain. However we leave out the root certificate because that would make it too easy for the recipient to import that root certificate. A root certificate should be installed only after due checks and thus it won't help to send it along with each message. */ #define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */ static int default_include_certs = DEFAULT_INCLUDE_CERTS; /* Whether the chain mode shall be used for validation. */ static int default_validation_model; /* The default cipher algo. */ #define DEFAULT_CIPHER_ALGO "AES256" static char *build_list (const char *text, const char *(*mapf)(int), int (*chkf)(int)); static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void emergency_cleanup (void); static int open_read (const char *filename); static estream_t open_es_fread (const char *filename, const char *mode); static estream_t open_es_fwrite (const char *filename); static void run_protect_tool (int argc, char **argv); static int our_pk_test_algo (int algo) { switch (algo) { case GCRY_PK_RSA: case GCRY_PK_ECDSA: case GCRY_PK_EDDSA: return gcry_pk_test_algo (algo); default: return 1; } } static int our_cipher_test_algo (int algo) { switch (algo) { case GCRY_CIPHER_3DES: case GCRY_CIPHER_AES128: case GCRY_CIPHER_AES192: case GCRY_CIPHER_AES256: case GCRY_CIPHER_SERPENT128: case GCRY_CIPHER_SERPENT192: case GCRY_CIPHER_SERPENT256: case GCRY_CIPHER_SEED: case GCRY_CIPHER_CAMELLIA128: case GCRY_CIPHER_CAMELLIA192: case GCRY_CIPHER_CAMELLIA256: return gcry_cipher_test_algo (algo); default: return 1; } } static int our_md_test_algo (int algo) { switch (algo) { case GCRY_MD_MD5: case GCRY_MD_SHA1: case GCRY_MD_RMD160: case GCRY_MD_SHA224: case GCRY_MD_SHA256: case GCRY_MD_SHA384: case GCRY_MD_SHA512: case GCRY_MD_WHIRLPOOL: return gcry_md_test_algo (algo); default: return 1; } } /* nPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers; static char *ver_gcry, *ver_ksba; const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGSM@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGSM@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @GPGSM@ [options] [files]\n" "Sign, check, encrypt or decrypt using the S/MIME protocol\n" "Default operation depends on the input data\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 21: if (!ver_ksba) ver_ksba = make_libversion ("libksba", ksba_check_version); p = ver_ksba; break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!ciphers) ciphers = build_list ("Cipher: ", gnupg_cipher_algo_name, our_cipher_test_algo ); p = ciphers; break; case 35: if (!pubkeys) pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, our_pk_test_algo ); p = pubkeys; break; case 36: if (!digests) digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); p = digests; break; default: p = NULL; break; } return p; } static char * build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) { int i; size_t n=strlen(text)+2; char *list, *p; if (maybe_setuid) { gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ } for (i=1; i < 400; i++ ) if (!chkf(i)) n += strlen(mapf(i)) + 2; list = xmalloc (21 + n); *list = 0; for (p=NULL, i=1; i < 400; i++) { if (!chkf(i)) { if( !p ) p = stpcpy (list, text ); else p = stpcpy (p, ", "); p = stpcpy (p, mapf(i) ); } } if (p) strcpy (p, "\n" ); return list; } /* Set the file pointer into binary mode if required. */ static void set_binary (FILE *fp) { #ifdef HAVE_DOSISH_SYSTEM setmode (fileno (fp), O_BINARY); #else (void)fp; #endif } static void wrong_args (const char *text) { fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text); gpgsm_exit (2); } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Setup the debugging. With a DEBUG_LEVEL of NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; and individual debugging flags will be added on top. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); gpgsm_exit (2); } opt.debug |= debug_value; if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug) 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); /* minip12.c may be used outside of GnuPG, thus we don't have the * opt structure over there. */ p12_set_verbosity (opt.verbose); } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values cmd = *ret_cmd; if (!cmd || cmd == new_cmd) cmd = new_cmd; else if ( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if ( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if ( (cmd == aSign && new_cmd == aClearsign) || (cmd == aClearsign && new_cmd == aSign) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); gpgsm_exit(2); } *ret_cmd = cmd; } /* Helper to add recipients to a list. */ static void do_add_recipient (ctrl_t ctrl, const char *name, certlist_t *recplist, int is_encrypt_to, int recp_required) { int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to); if (rc) { if (recp_required) { log_error ("can't encrypt to '%s': %s\n", name, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), name, NULL); } else log_info (_("Note: won't be able to encrypt to '%s': %s\n"), name, gpg_strerror (rc)); } } static void parse_validation_model (const char *model) { int i = gpgsm_parse_validation_model (model); if (i == -1) log_error (_("unknown validation model '%s'\n"), model); else default_validation_model = i; } int main ( int argc, char **argv) { gpg_error_t err = 0; gpgrt_argparse_t pargs; int orig_argc; char **orig_argv; /* char *username;*/ int may_coredump; strlist_t sl, remusr= NULL, locusr=NULL; strlist_t nrings=NULL; int detached_sig = 0; char *last_configname = NULL; const char *configname = NULL; /* NULL or points to last_configname. * NULL also indicates that we are * processing options from the cmdline. */ int debug_argparser = 0; int no_more_options = 0; int default_keyring = 1; char *logfile = NULL; char *auditlog = NULL; char *htmlauditlog = NULL; int greeting = 0; int nogreeting = 0; int debug_wait = 0; int use_random_seed = 1; int no_common_certs_import = 0; int with_fpr = 0; const char *forced_digest_algo = NULL; const char *extra_digest_algo = NULL; enum cmd_and_opt_values cmd = 0; struct server_control_s ctrl; certlist_t recplist = NULL; certlist_t signerlist = NULL; int do_not_setup_keys = 0; int recp_required = 0; estream_t auditfp = NULL; estream_t htmlauditfp = NULL; struct assuan_malloc_hooks malloc_hooks; int pwfd = -1; static const char *homedirvalue; static const char *changeuser; early_system_init (); gnupg_reopen_std (GPGSM_NAME); /* trap_unaligned ();*/ gnupg_rl_initialize (); gpgrt_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 secmem_init() somewhere after the option parsing */ log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); /* Check that the libraries are suitable. Do it here because the option parse may need services of the library */ if (!ksba_check_version (NEED_KSBA_VERSION) ) log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba", NEED_KSBA_VERSION, ksba_check_version (NULL) ); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); gnupg_init_signals (0, emergency_cleanup); dotlock_create (NULL, 0); /* Register lockfile cleanup. */ /* Tell the compliance module who we are. */ gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPGSM); opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); /* Note: If you change this default cipher algorithm , please remember to update the Gpgconflist entry as well. */ opt.def_cipher_algoid = DEFAULT_CIPHER_ALGO; /* First check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oNoOptions: /* Set here here because the homedir would otherwise be * created before main option parsing starts. */ opt.no_homedir_creation = 1; break; case oHomedir: homedirvalue = pargs.r.ret_str; break; case oChUid: changeuser = pargs.r.ret_str; break; case aCallProtectTool: /* Make sure that --version and --help are passed to the * protect-tool. */ goto leave_cmdline_parser; } } leave_cmdline_parser: /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Change UID and then set homedir. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ gnupg_set_homedir (homedirvalue); /* Setup a default control structure for command line mode */ memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); ctrl.no_server = 1; ctrl.status_fd = -1; /* No status output. */ ctrl.autodetect_encoding = 1; /* Set the default policy file */ opt.policy_file = make_filename (gnupg_homedir (), "policies.txt", NULL); /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); /* We are re-using the struct, thus the reset flag. We OR the * flags so that the internal intialized flag won't be cleared. */ argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (!no_more_options && gpgrt_argparser (&pargs, opts, GPGSM_NAME EXTSEP_S "conf")) { switch (pargs.r_opt) { case ARGPARSE_CONFFILE: if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; break; case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); do_not_setup_keys = 1; default_keyring = 0; nogreeting = 1; break; case aServer: opt.batch = 1; set_cmd (&cmd, aServer); break; case aCallDirmngr: opt.batch = 1; set_cmd (&cmd, aCallDirmngr); do_not_setup_keys = 1; break; case aCallProtectTool: opt.batch = 1; set_cmd (&cmd, aCallProtectTool); no_more_options = 1; /* Stop parsing. */ do_not_setup_keys = 1; break; case aDeleteKey: set_cmd (&cmd, aDeleteKey); /*greeting=1;*/ do_not_setup_keys = 1; break; case aDetachedSign: detached_sig = 1; set_cmd (&cmd, aSign ); break; case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; do_not_setup_keys = 1; break; case aImport: case aSendKeys: case aRecvKeys: case aExport: case aExportSecretKeyP12: case aExportSecretKeyP8: case aExportSecretKeyRaw: case aShowCerts: case aDumpKeys: case aDumpChain: case aDumpExternalKeys: case aDumpSecretKeys: case aListKeys: case aListExternalKeys: case aListSecretKeys: case aListChain: case aLearnCard: case aPasswd: case aKeydbClearSomeCertFlags: do_not_setup_keys = 1; set_cmd (&cmd, pargs.r_opt); break; case aEncr: recp_required = 1; set_cmd (&cmd, pargs.r_opt); break; case aSym: case aDecrypt: case aSign: case aClearsign: case aVerify: set_cmd (&cmd, pargs.r_opt); break; /* Output encoding selection. */ case oArmor: ctrl.create_pem = 1; break; case oBase64: ctrl.create_pem = 0; ctrl.create_base64 = 1; break; case oNoArmor: ctrl.create_pem = 0; ctrl.create_base64 = 0; break; case oP12Charset: opt.p12_charset = pargs.r.ret_str; break; case oPassphraseFD: - pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); + pwfd = translate_sys2libc_fdstr (pargs.r.ret_str, 0); break; case oPinentryMode: opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); if (opt.pinentry_mode == -1) log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); break; case oRequestOrigin: opt.request_origin = parse_request_origin (pargs.r.ret_str); if (opt.request_origin == -1) log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str); break; /* Input encoding selection. */ case oAssumeArmor: ctrl.autodetect_encoding = 0; ctrl.is_pem = 1; ctrl.is_base64 = 0; break; case oAssumeBase64: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 1; break; case oAssumeBinary: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 0; break; case oDisableCRLChecks: opt.no_crl_check = 1; break; case oEnableCRLChecks: opt.no_crl_check = 0; break; case oDisableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 1; break; case oEnableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 0; break; case oForceCRLRefresh: opt.force_crl_refresh = 1; break; case oEnableIssuerBasedCRLCheck: opt.enable_issuer_based_crl_check = 1; break; case oDisableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 0; break; case oEnableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 1; break; case oIncludeCerts: ctrl.include_certs = default_include_certs = pargs.r.ret_int; break; case oPolicyFile: xfree (opt.policy_file); if (*pargs.r.ret_str) opt.policy_file = xstrdup (pargs.r.ret_str); else opt.policy_file = NULL; break; case oDisablePolicyChecks: opt.no_policy_check = 1; break; case oEnablePolicyChecks: opt.no_policy_check = 0; break; case oAutoIssuerKeyRetrieve: opt.auto_issuer_key_retrieve = 1; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; case oLogTime: opt_log_time = 1; break; case oAuditLog: auditlog = pargs.r.ret_str; break; case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break; case oBatch: opt.batch = 1; greeting = 0; break; case oNoBatch: opt.batch = 0; break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; case oUseKeyboxd: opt.use_keyboxd = 1; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oDebugAllowCoreDump: may_coredump = enable_core_dumps (); break; case oDebugNoChainValidation: opt.no_chain_validation = 1; break; case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break; case oDebugForceECDHSHA1KDF: opt.force_ecdh_sha1kdf = 1; break; case oCompatibilityFlags: if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags, compatibility_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oStatusFD: - ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1); + ctrl.status_fd = translate_sys2libc_fdstr (pargs.r.ret_str, 1); break; case oLoggerFD: - log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + log_set_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oWithMD5Fingerprint: opt.with_md5_fingerprint=1; /*fall through*/ case oWithFingerprint: with_fpr=1; /*fall through*/ case aFingerprint: opt.fingerprint++; break; case oWithKeygrip: opt.with_keygrip = 1; break; case oWithKeyScreening: opt.with_key_screening = 1; break; case oNoPrettyDN: opt.no_pretty_dn = 1; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oChUid: break; /* Command line only (see above). */ case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs.r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs.r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs.r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs.r.ret_str); break; case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oDisableDirmngr: opt.disable_dirmngr = 1; break; case oPreferSystemDirmngr: /* Obsolete */; break; case oProtectToolProgram: opt.protect_tool_program = pargs.r.ret_str; 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 oNoDefKeyring: default_keyring = 0; break; case oNoGreeting: nogreeting = 1; break; case oDefaultKey: if (*pargs.r.ret_str) { xfree (opt.local_user); opt.local_user = xstrdup (pargs.r.ret_str); } break; case oDefRecipient: if (*pargs.r.ret_str) opt.def_recipient = xstrdup (pargs.r.ret_str); break; case oDefRecipientSelf: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oWithKeyData: opt.with_key_data=1; /* fall through */ case oWithColons: ctrl.with_colons = 1; break; case oWithSecret: ctrl.with_secret = 1; break; case oWithValidation: ctrl.with_validation=1; break; case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break; case oSkipVerify: opt.skip_verify=1; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptTo: /* Store the recipient in the second list */ sl = add_to_strlist (&remusr, pargs.r.ret_str); sl->flags = 1; break; case oRecipient: /* store the recipient */ add_to_strlist ( &remusr, pargs.r.ret_str); break; case oUser: /* Store the local users, the first one is the default */ if (!opt.local_user) opt.local_user = xstrdup (pargs.r.ret_str); add_to_strlist (&locusr, pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oCipherAlgo: opt.def_cipher_algoid = pargs.r.ret_str; break; case oDisableCipherAlgo: { int algo = gcry_cipher_map_name (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); } break; case oDigestAlgo: forced_digest_algo = pargs.r.ret_str; break; case oExtraDigestAlgo: extra_digest_algo = pargs.r.ret_str; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oNoCommonCertsImport: no_common_certs_import = 1; break; case oEnableSpecialFilenames: enable_special_filenames (); break; case oValidationModel: parse_validation_model (pargs.r.ret_str); break; case oKeyServer: append_to_strlist (&opt.keyserver, pargs.r.ret_str); break; case oKeyServer_deprecated: obsolete_option (configname, pargs.lineno, "ldapserver"); break; case oIgnoreCertExtension: add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str); break; case oIgnoreCertWithOID: add_to_strlist (&opt.ignore_cert_with_oid, pargs.r.ret_str); break; case oAuthenticode: opt.authenticode = 1; break; case oAttribute: add_to_strlist (&opt.attributes, pargs.r.ret_str); break; case oNoAutostart: opt.autostart = 0; break; case oCompliance: { struct gnupg_compliance_option compliance_options[] = { { "gnupg", CO_GNUPG }, { "de-vs", CO_DE_VS } }; int compliance = gnupg_parse_compliance_option (pargs.r.ret_str, compliance_options, DIM (compliance_options), opt.quiet); if (compliance < 0) log_inc_errorcount (); /* Force later termination. */ opt.compliance = compliance; } break; case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; case oRequireCompliance: opt.require_compliance = 1; break; case oKbxBufferSize: keybox_set_buffersize (pargs.r.ret_ulong, 0); break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else { pargs.err = ARGPARSE_PRINT_ERROR; /* The argparse function calls a plain exit and thus we * need to print a status here. */ gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); } break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (!last_configname) opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (), GPGSM_NAME EXTSEP_S "conf", NULL); else opt.config_filename = last_configname; if (log_get_errorcount(0)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit(2); } /* Process common component options. */ if (parse_comopt (GNUPG_MODULE_NAME_GPGSM, debug_argparser)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit(2); } if (opt.use_keyboxd) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "use-keyboxd"); opt.use_keyboxd = comopt.use_keyboxd; /* Override. */ if (opt.keyboxd_program) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "keyboxd-program"); if (!opt.keyboxd_program && comopt.keyboxd_program) { opt.keyboxd_program = comopt.keyboxd_program; comopt.keyboxd_program = NULL; } if (comopt.no_autostart) opt.autostart = 0; if (pwfd != -1) /* Read the passphrase now. */ read_passphrase_from_fd (pwfd); /* Now that we have the options parsed we need to update the default control structure. */ gpgsm_init_default_ctrl (&ctrl); if (nogreeting) greeting = 0; if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14) ); es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); log_info ("It is only intended for test purposes and should NOT be\n"); log_info ("used in a production environment or with production keys!\n"); } #endif if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* if (opt.qualsig_approval && !opt.quiet) */ /* log_info (_("This software has officially been approved to " */ /* "create and verify\n" */ /* "qualified signatures according to German law.\n")); */ if (logfile && cmd == aServer) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } else if (opt_log_time) log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY |GPGRT_LOG_WITH_TIME)); if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); dump_isotime (tbuf); log_printf ("\n"); } /* 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]); } /*FIXME if (opt.batch) */ /* tty_batchmode (1); */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); set_debug (); if (opt.verbose) /* Print the compatibility flags. */ parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags); gnupg_set_compliance_extra_info (opt.min_rsa_length); /* Although we always use gpgsm_exit, we better install a regular exit handler so that at least the secure memory gets wiped out. */ if (atexit (emergency_cleanup)) { log_error ("atexit failed\n"); gpgsm_exit (2); } /* Must do this after dropping setuid, because the mapping functions may try to load an module and we may have disabled an algorithm. We remap the commonly used algorithms to the OIDs for convenience. We need to work with the OIDs because they are used to check whether the encryption mode is actually available. */ if (!strcmp (opt.def_cipher_algoid, "3DES") ) opt.def_cipher_algoid = "1.2.840.113549.3.7"; else if (!strcmp (opt.def_cipher_algoid, "AES") || !strcmp (opt.def_cipher_algoid, "AES128")) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2"; else if (!strcmp (opt.def_cipher_algoid, "AES192") ) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22"; else if (!strcmp (opt.def_cipher_algoid, "AES256") ) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT") || !strcmp (opt.def_cipher_algoid, "SERPENT128") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT256") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42"; else if (!strcmp (opt.def_cipher_algoid, "SEED") ) opt.def_cipher_algoid = "1.2.410.200004.1.4"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA") || !strcmp (opt.def_cipher_algoid, "CAMELLIA128") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4"; if (cmd != aGPGConfList) { if ( !gcry_cipher_map_name (opt.def_cipher_algoid) || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) log_error (_("selected cipher algorithm is invalid\n")); if (forced_digest_algo) { opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo); if (our_md_test_algo(opt.forced_digest_algo) ) log_error (_("selected digest algorithm is invalid\n")); } if (extra_digest_algo) { opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo); if (our_md_test_algo (opt.extra_digest_algo) ) log_error (_("selected digest algorithm is invalid\n")); } } /* Check our chosen algorithms against the list of allowed * algorithms in the current compliance mode, and fail hard if it is * not. This is us being nice to the user informing her early that * the chosen algorithms are not available. We also check and * enforce this right before the actual operation. */ if (! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr, gcry_cipher_map_name (opt.def_cipher_algoid), GCRY_CIPHER_MODE_NONE) && ! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr, gcry_cipher_mode_from_oid (opt.def_cipher_algoid), GCRY_CIPHER_MODE_NONE)) log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), opt.def_cipher_algoid, gnupg_compliance_option_string (opt.compliance)); if (forced_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aClearsign, opt.forced_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), forced_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (extra_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aClearsign, opt.extra_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), extra_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (log_get_errorcount(0)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-postprocessing", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit (2); } /* Set the random seed file. */ if (use_random_seed) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); xfree(p); } if (!cmd && opt.fingerprint && !with_fpr) set_cmd (&cmd, aListKeys); /* If no pinentry is expected shunt * gnupg_allow_set_foregound_window to avoid useless error * messages on Windows. */ if (opt.pinentry_mode != PINENTRY_MODE_ASK) { gnupg_inhibit_set_foregound_window (1); } /* Add default keybox. */ if (!nrings && default_keyring && !opt.use_keyboxd) { int created; keydb_add_resource (&ctrl, "pubring.kbx", 0, &created); if (created && !no_common_certs_import) { /* Import the standard certificates for a new default keybox. */ char *filelist[2]; filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL); filelist[1] = NULL; if (!gnupg_access (filelist[0], F_OK)) { log_info (_("importing common certificates '%s'\n"), filelist[0]); gpgsm_import_files (&ctrl, 1, filelist, open_read); } xfree (filelist[0]); } } if (!opt.use_keyboxd) { for (sl = nrings; sl; sl = sl->next) keydb_add_resource (&ctrl, sl->d, 0, NULL); } FREE_STRLIST(nrings); /* Prepare the audit log feature for certain commands. */ if (auditlog || htmlauditlog) { switch (cmd) { case aEncr: case aSign: case aDecrypt: case aVerify: audit_release (ctrl.audit); ctrl.audit = audit_new (); if (auditlog) auditfp = open_es_fwrite (auditlog); if (htmlauditlog) htmlauditfp = open_es_fwrite (htmlauditlog); break; default: break; } } if (!do_not_setup_keys) { int errcount = log_get_errorcount (0); for (sl = locusr; sl ; sl = sl->next) { int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0); if (rc) { log_error (_("can't sign using '%s': %s\n"), sl->d, gpg_strerror (rc)); gpgsm_status2 (&ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), sl->d, NULL); gpgsm_status2 (&ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), sl->d, NULL); } } /* Build the recipient list. We first add the regular ones and then the encrypt-to ones because the underlying function will silently ignore duplicates and we can't allow keeping a duplicate which is flagged as encrypt-to as the actually encrypt function would then complain about no (regular) recipients. */ for (sl = remusr; sl; sl = sl->next) if (!(sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required); if (!opt.no_encrypt_to) { for (sl = remusr; sl; sl = sl->next) if ((sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required); } /* We do not require a recipient for decryption but because * recipients and signers are always checked and log_error is * sometimes used (for failed signing keys or due to a failed * CRL checking) that would have bumbed up the error counter. * We clear the counter in the decryption case because there is * no reason to force decryption to fail. */ if (cmd == aDecrypt && !errcount) log_get_errorcount (1); /* clear counter */ } if (log_get_errorcount(0)) gpgsm_exit(1); /* Must stop for invalid recipients. */ /* Dispatch command. */ switch (cmd) { case aGPGConfList: { /* List default option values in the GPG Conf format. */ es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_INCLUDE_CERTS); es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_CIPHER_ALGO); es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT); /* The next one is an info only item and should match what proc_parameters actually implements. */ es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "RSA-3072"); } break; case aGPGConfTest: /* This is merely a dummy command to test whether the configuration file is valid. */ break; case aServer: if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } gpgsm_server (recplist); break; case aCallDirmngr: if (!argc) wrong_args ("--call-dirmngr {args}"); else if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) gpgsm_exit (1); break; case aCallProtectTool: run_protect_tool (argc, argv); break; case aEncr: /* Encrypt the given file. */ { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); set_binary (stdin); if (!argc) /* Source is stdin. */ err = gpgsm_encrypt (&ctrl, recplist, 0, fp); else if (argc == 1) /* Source is the given file. */ err = gpgsm_encrypt (&ctrl, recplist, open_read (*argv), fp); else wrong_args ("--encrypt [datafile]"); if (err) gpgrt_fcancel (fp); else es_fclose (fp); } break; case aSign: /* Sign the given file. */ { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); /* Fixme: We should also allow concatenation of multiple files for signing because that is what gpg does.*/ set_binary (stdin); if (!argc) /* Create from stdin. */ err = gpgsm_sign (&ctrl, signerlist, 0, detached_sig, fp); else if (argc == 1) /* From file. */ err = gpgsm_sign (&ctrl, signerlist, open_read (*argv), detached_sig, fp); else wrong_args ("--sign [datafile]"); #if GPGRT_VERSION_NUMBER >= 0x012700 /* >= 1.39 */ if (err) gpgrt_fcancel (fp); else es_fclose (fp); #else (void)err; es_fclose (fp); #endif } break; case aSignEncr: /* sign and encrypt the given file */ log_error ("the command '%s' has not yet been implemented\n", "--sign --encrypt"); gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_NOT_IMPLEMENTED)); break; case aClearsign: /* make a clearsig */ log_error ("the command '%s' has not yet been implemented\n", "--clearsign"); gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_NOT_IMPLEMENTED)); break; case aVerify: { estream_t fp = NULL; set_binary (stdin); if (argc == 2 && opt.outfile) log_info ("option --output ignored for a detached signature\n"); else if (opt.outfile) fp = open_es_fwrite (opt.outfile); if (!argc) gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ else if (argc == 1) gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ else if (argc == 2) /* detached signature (sig, detached) */ gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); else wrong_args ("--verify [signature [detached_data]]"); es_fclose (fp); } break; case aDecrypt: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); set_binary (stdin); if (!argc) err = gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */ else if (argc == 1) err = gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */ else wrong_args ("--decrypt [filename]"); if (err) gpgrt_fcancel (fp); else es_fclose (fp); } break; case aDeleteKey: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_delete (&ctrl, sl); free_strlist(sl); break; case aListChain: case aDumpChain: ctrl.with_chain = 1; /* fall through */ case aListKeys: case aDumpKeys: case aListExternalKeys: case aDumpExternalKeys: case aListSecretKeys: case aDumpSecretKeys: { unsigned int mode; estream_t fp; switch (cmd) { case aListChain: case aListKeys: mode = (0 | 0 | (1<<6)); break; case aDumpChain: case aDumpKeys: mode = (256 | 0 | (1<<6)); break; case aListExternalKeys: mode = (0 | 0 | (1<<7)); break; case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break; case aListSecretKeys: mode = (0 | 2 | (1<<6)); break; case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break; default: BUG(); } fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, fp, mode); free_strlist(sl); es_fclose (fp); } break; case aShowCerts: { estream_t fp; fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); gpgsm_show_certs (&ctrl, argc, argv, fp); es_fclose (fp); } break; case aKeygen: /* Generate a key; well kind of. */ { estream_t fpin = NULL; estream_t fpout; if (opt.batch) { if (!argc) /* Create from stdin. */ fpin = open_es_fread ("-", "r"); else if (argc == 1) /* From file. */ fpin = open_es_fread (*argv, "r"); else wrong_args ("--generate-key --batch [parmfile]"); } fpout = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (fpin) gpgsm_genkey (&ctrl, fpin, fpout); else gpgsm_gencertreq_tty (&ctrl, fpout); es_fclose (fpout); } break; case aImport: gpgsm_import_files (&ctrl, argc, argv, open_read); break; case aExport: { estream_t fp; fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_export (&ctrl, sl, fp); free_strlist(sl); es_fclose (fp); } break; case aExportSecretKeyP12: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 0); else wrong_args ("--export-secret-key-p12 KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aExportSecretKeyP8: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 1); else wrong_args ("--export-secret-key-p8 KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aExportSecretKeyRaw: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 2); else wrong_args ("--export-secret-key-raw KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aSendKeys: case aRecvKeys: log_error ("this command has not yet been implemented\n"); break; case aLearnCard: if (argc) wrong_args ("--learn-card"); else { int rc = gpgsm_agent_learn (&ctrl); if (rc) log_error ("error learning card: %s\n", gpg_strerror (rc)); } break; case aPasswd: if (argc != 1) wrong_args ("--change-passphrase "); else { int rc; ksba_cert_t cert = NULL; char *grip = NULL; rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert, 0); if (rc) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) rc = gpg_error (GPG_ERR_BUG); else { char *desc = gpgsm_format_keydesc (cert); rc = gpgsm_agent_passwd (&ctrl, grip, desc); xfree (desc); } if (rc) log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); xfree (grip); ksba_cert_release (cert); } break; case aKeydbClearSomeCertFlags: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); keydb_clear_some_cert_flags (&ctrl, sl); free_strlist(sl); break; default: log_error (_("invalid command (there is no implicit command)\n")); gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_MISSING_ACTION)); break; } /* Print the audit result if needed. */ if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp)) { if (auditlog && auditfp) audit_print_result (ctrl.audit, auditfp, 0); if (htmlauditlog && htmlauditfp) audit_print_result (ctrl.audit, htmlauditfp, 1); audit_release (ctrl.audit); ctrl.audit = NULL; es_fclose (auditfp); es_fclose (htmlauditfp); } /* cleanup */ gpgsm_deinit_default_ctrl (&ctrl); free_strlist (opt.keyserver); opt.keyserver = NULL; gpgsm_release_certlist (recplist); gpgsm_release_certlist (signerlist); FREE_STRLIST (remusr); FREE_STRLIST (locusr); gpgsm_exit(0); return 8; /*NOTREACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void gpgsm_exit (int rc) { gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); 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 ); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; exit (rc); } void gpgsm_init_default_ctrl (struct server_control_s *ctrl) { ctrl->include_certs = default_include_certs; ctrl->use_ocsp = opt.enable_ocsp; ctrl->validation_model = default_validation_model; ctrl->offline = opt.disable_dirmngr; ctrl->revoked_at[0] = 0; ctrl->revocation_reason = NULL; } /* This function is called to deinitialize a control object. The * control object is is not released, though. */ void gpgsm_deinit_default_ctrl (ctrl_t ctrl) { gpgsm_keydb_deinit_session_data (ctrl); xfree (ctrl->revocation_reason); ctrl->revocation_reason = NULL; } int gpgsm_parse_validation_model (const char *model) { if (!ascii_strcasecmp (model, "shell") ) return 0; else if ( !ascii_strcasecmp (model, "chain") ) return 1; else if ( !ascii_strcasecmp (model, "steed") ) return 2; else return -1; } /* Open the FILENAME for read and return the file descriptor. Stop with an error message in case of problems. "-" denotes stdin and if special filenames are allowed the given fd is opened instead. */ static int open_read (const char *filename) { int fd; if (filename[0] == '-' && !filename[1]) { set_binary (stdin); return 0; /* stdin */ } fd = check_special_filename (filename, 0, 0); if (fd != -1) return fd; fd = gnupg_open (filename, O_RDONLY | O_BINARY, 0); if (fd == -1) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fd; } /* Same as open_read but return an estream_t. */ static estream_t open_es_fread (const char *filename, const char *mode) { int fd; estream_t fp; if (filename[0] == '-' && !filename[1]) fd = fileno (stdin); else fd = check_special_filename (filename, 0, 0); if (fd != -1) { fp = es_fdopen_nc (fd, mode); if (!fp) { log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); gpgsm_exit (2); } return fp; } fp = es_fopen (filename, mode); if (!fp) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fp; } /* Open FILENAME for fwrite and return an extended stream. Stop with an error message in case of problems. "-" denotes stdout and if special filenames are allowed the given fd is opened instead. Caller must close the returned stream. */ static estream_t open_es_fwrite (const char *filename) { int fd; estream_t fp; if (filename[0] == '-' && !filename[1]) { fflush (stdout); fp = es_fdopen_nc (fileno(stdout), "wb"); return fp; } fd = check_special_filename (filename, 1, 0); if (fd != -1) { fp = es_fdopen_nc (fd, "wb"); if (!fp) { log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); gpgsm_exit (2); } return fp; } fp = es_fopen (filename, "wb"); if (!fp) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fp; } static void run_protect_tool (int argc, char **argv) { #ifdef HAVE_W32_SYSTEM (void)argc; (void)argv; #else const char *pgm; char **av; int i; if (!opt.protect_tool_program || !*opt.protect_tool_program) pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); else pgm = opt.protect_tool_program; av = xcalloc (argc+2, sizeof *av); av[0] = strrchr (pgm, '/'); if (!av[0]) av[0] = xstrdup (pgm); for (i=1; argc; i++, argc--, argv++) av[i] = *argv; av[i] = NULL; execv (pgm, av); log_error ("error executing '%s': %s\n", pgm, strerror (errno)); #endif /*!HAVE_W32_SYSTEM*/ gpgsm_exit (2); } diff --git a/tools/gpg-auth.c b/tools/gpg-auth.c index f433ba220..6de3494ad 100644 --- a/tools/gpg-auth.c +++ b/tools/gpg-auth.c @@ -1,999 +1,999 @@ /* gpg-auth.c - Authenticate using GnuPG * Copyright (C) 2022 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/session-env.h" #include "../common/membuf.h" #include "../common/exechelp.h" /* We keep all global options in the structure OPT. */ struct { int interactive; int verbose; unsigned int debug; int quiet; int with_colons; const char *agent_program; int autostart; int use_scd_directly; /* Options passed to the gpg-agent: */ char *lc_ctype; char *lc_messages; } opt; /* Debug values and macros. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oAgentProgram, oStatusFD, oWithColons, oNoAutostart, oLCctype, oLCmessages, oUseSCDDirectly, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oUseSCDDirectly, "use-scdaemon-directly", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-auth"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-auth" " [options] (-h for help)"); break; case 41: p = ("Syntax: gpg-auth" " [options] \n\n" "Tool to authenticate a user using a smartcard.\n" "Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: - gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + gnupg_set_status_fd (translate_sys2libc_fdstr (pargs->r.ret_str, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; case oUseSCDDirectly: opt.use_scd_directly = 1; break; default: pargs->err = ARGPARSE_PRINT_ERROR; break; } } } struct ga_key_list { struct ga_key_list *next; char keygrip[41]; /* Keygrip to identify a key. */ size_t pubkey_len; char *pubkey; /* Public key in SSH format. */ char *comment; }; /* Local prototypes. */ static gpg_error_t scd_passwd_reset (assuan_context_t ctx, const char *keygrip); static gpg_error_t ga_scd_connect (assuan_context_t *r_scd_ctx, int use_agent); static gpg_error_t ga_scd_get_auth_keys (assuan_context_t ctx, struct ga_key_list **r_key_list); static gpg_error_t ga_filter_by_authorized_keys (const char *user, struct ga_key_list **r_key_list); static void ga_release_auth_keys (struct ga_key_list *key_list); static gpg_error_t scd_pkauth (assuan_context_t ctx, const char *keygrip); static gpg_error_t authenticate (assuan_context_t ctx, struct ga_key_list *key_list); static int getpin (const char *comment, const char *info, char *buf, size_t *r_len); /* gpg-auth main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; assuan_context_t scd_ctx = NULL; struct ga_key_list *key_list = NULL; const char *user; gnupg_reopen_std ("gpg-auth"); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-auth", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) exit (2); if (argc != 0) gpgrt_usage (1); /* Never returns. */ if (opt.use_scd_directly) { user = getenv ("PAM_USER"); if (user == NULL) exit (2); } else user = NULL; err = ga_scd_connect (&scd_ctx, opt.use_scd_directly); if (!err) err = ga_scd_get_auth_keys (scd_ctx, &key_list); if (!err) err = ga_filter_by_authorized_keys (user, &key_list); if (!err) err = authenticate (scd_ctx, key_list); ga_release_auth_keys (key_list); if (scd_ctx) assuan_release (scd_ctx); if (err) exit (1); return 0; } static gpg_error_t authenticate (assuan_context_t ctx, struct ga_key_list *key_list) { gpg_error_t err; while (key_list) { err = scd_passwd_reset (ctx, key_list->keygrip); if (err) return err; assuan_set_pointer (ctx, key_list->comment); err = scd_pkauth (ctx, key_list->keygrip); if (!err) /* Success! */ return 0; key_list = key_list->next; } return gpg_error (GPG_ERR_NOT_FOUND); } static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return gpg_error_from_syserror (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Helper function, which is used by scd_connect. Try to retrieve the SCDaemon's socket name from the gpg-agent context CTX. On success, *SOCKET_NAME is filled with a copy of the socket name. Return proper error code or zero on success. */ static gpg_error_t agent_scd_getinfo_socket_name (assuan_context_t ctx, char **socket_name) { membuf_t data; gpg_error_t err = 0; unsigned char *databuf; size_t datalen; init_membuf (&data, 256); *socket_name = NULL; err = assuan_transact (ctx, "SCD GETINFO socket_name", put_membuf_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (!err) { if (databuf && datalen) { char *res = xtrymalloc (datalen + 1); if (!res) err = gpg_error_from_syserror (); else { memcpy (res, databuf, datalen); res[datalen] = 0; *socket_name = res; } } } xfree (databuf); return err; } /* Callback parameter for learn card */ struct learn_parm_s { 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; }; /* Connect to the agent and send the standard options. */ static gpg_error_t start_agent (assuan_context_t *ctx_p) { gpg_error_t err; session_env_t session_env; session_env = session_env_new (); if (!session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); err = start_new_gpg_agent (ctx_p, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, NULL, NULL, session_env, opt.autostart, !opt.quiet, 0, NULL, NULL); session_env_release (session_env); return err; } static gpg_error_t scd_serialno (assuan_context_t ctx) { char *serialno = NULL; gpg_error_t err; err = assuan_transact (ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); xfree (serialno); return err; } static gpg_error_t scd_passwd_reset (assuan_context_t ctx, const char *keygrip) { char line[ASSUAN_LINELENGTH]; gpg_error_t err; snprintf (line, DIM(line), "PASSWD --clear OPENPGP.2 %s", keygrip); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } /* Connect to scdaemon by pipe or socket. Execute initial "SEREIALNO" command to enable all connected token under scdaemon control. */ static gpg_error_t ga_scd_connect (assuan_context_t *r_scd_ctx, int use_scd_directly) { assuan_context_t assuan_ctx; gpg_error_t err; err = assuan_new (&assuan_ctx); if (err) return err; if (!use_scd_directly) /* Use scdaemon under gpg-agent. */ { char *scd_socket_name = NULL; assuan_context_t ctx; err = start_agent (&ctx); if (err) return err; /* Note that if gpg-agent is there but no scdaemon yet, * gpg-agent automatically invokes scdaemon by this query * itself. */ err = agent_scd_getinfo_socket_name (ctx, &scd_socket_name); assuan_release (ctx); if (!err) err = assuan_socket_connect (assuan_ctx, scd_socket_name, 0, 0); if (!err && DBG_IPC) log_debug ("got scdaemon socket name from gpg-agent, " "connected to socket '%s'", scd_socket_name); xfree (scd_socket_name); } else { const char *scd_path; const char *pgmname; const char *argv[3]; int no_close_list[2]; scd_path = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); if (!(pgmname = strrchr (scd_path, '/'))) pgmname = scd_path; else pgmname++; /* Fill argument vector for scdaemon. */ argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; no_close_list[0] = assuan_fd_from_posix_fd (fileno (stderr)); no_close_list[1] = ASSUAN_INVALID_FD; /* Connect to the scdaemon */ err = assuan_pipe_connect (assuan_ctx, scd_path, argv, no_close_list, NULL, NULL, 0); if (err) { log_error ("could not spawn scdaemon: %s\n", gpg_strerror (err)); return err; } if (DBG_IPC) log_debug ("spawned a new scdaemon (path: '%s')", scd_path); } if (err) assuan_release (assuan_ctx); else { scd_serialno (assuan_ctx); *r_scd_ctx = assuan_ctx; } return err; } /* Handle the NEEDPIN inquiry. */ static gpg_error_t inq_needpin (void *opaque, const char *line) { assuan_context_t ctx = opaque; const char *s; char *pin; size_t pinlen; int rc; const char *comment = assuan_get_pointer (ctx); rc = 0; if ((s = has_leading_keyword (line, "NEEDPIN"))) { line = s; pinlen = 90; pin = gcry_malloc_secure (pinlen); if (!pin) return out_of_core (); rc = getpin (comment, line, pin, &pinlen); if (!rc) { assuan_begin_confidential (ctx); rc = assuan_send_data (ctx, pin, pinlen); assuan_end_confidential (ctx); } wipememory (pin, pinlen); xfree (pin); } else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT"))) { if (comment) { int msg_len = 27 + strlen (comment); fprintf (stdout, "i %d\n", msg_len); fprintf (stdout, "Please use PINPAD for KEY: %s\n", comment); fflush (stdout); } else { fputs ("i 18\n", stdout); fputs ("Please use PINPAD!\n", stdout); fflush (stdout); } } else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT"))) { ; } else { log_error ("unsupported inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return gpg_error (rc); } struct card_keyinfo_parm_s { int error; struct ga_key_list *list; }; /* Callback function for scd_keyinfo_list. */ static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; struct ga_key_list *keyinfo = NULL; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen)) { const char *s; int n; struct ga_key_list **l_p = &parm->list; /* It's going to append the information at the end. */ while ((*l_p)) l_p = &(*l_p)->next; keyinfo = xtrycalloc (1, sizeof *keyinfo); if (!keyinfo) goto alloc_error; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (n != 40) goto parm_error; memcpy (keyinfo->keygrip, line, 40); keyinfo->keygrip[40] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (*line++ != 'T') goto parm_error; if (!*line) goto parm_error; while (spacep (line)) line++; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n) goto skip; skip: *l_p = keyinfo; } return err; alloc_error: xfree (keyinfo); if (!parm->error) parm->error = gpg_error_from_syserror (); return 0; parm_error: xfree (keyinfo); if (!parm->error) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); return 0; } /* Call the scdaemon to retrieve list of available keys on cards. On success, the allocated structure is stored at R_KEY_LIST. On error, an error code is returned and NULL is stored at R_KEY_LIST. */ static gpg_error_t scd_keyinfo_list (assuan_context_t ctx, struct ga_key_list **r_key_list) { int err; struct card_keyinfo_parm_s parm; memset (&parm, 0, sizeof parm); err = assuan_transact (ctx, "KEYINFO --list=auth", NULL, NULL, NULL, NULL, card_keyinfo_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *r_key_list = parm.list; else ga_release_auth_keys (parm.list); return err; } /* A variant of put_membuf_cb, which only put the second field. */ static gpg_error_t put_second_field_cb (void *opaque, const void *buf, size_t len) { char line[ASSUAN_LINELENGTH]; membuf_t *data = opaque; if (buf && len < ASSUAN_LINELENGTH) { const char *fields[3]; size_t field_len; memcpy (line, buf, len); if (split_fields (line, fields, DIM (fields)) < 2) return 0; field_len = strlen (fields[1]); put_membuf (data, fields[1], field_len); } return 0; } static gpg_error_t scd_get_pubkey (assuan_context_t ctx, struct ga_key_list *key) { char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *databuf; size_t datalen; gpg_error_t err = 0; init_membuf (&data, 256); snprintf (line, DIM(line), "READKEY --format=ssh %s", key->keygrip); err = assuan_transact (ctx, line, put_second_field_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (!err) { key->pubkey_len = datalen; key->pubkey = databuf; } else xfree (databuf); return err; } static gpg_error_t ga_scd_get_auth_keys (assuan_context_t ctx, struct ga_key_list **r_key_list) { gpg_error_t err; struct ga_key_list *kl, *key_list = NULL; /* Get list of auth keys with their keygrips. */ err = scd_keyinfo_list (ctx, &key_list); /* And retrieve public key for each key. */ kl = key_list; while (kl) { err = scd_get_pubkey (ctx, kl); if (err) break; kl = kl->next; } if (err) ga_release_auth_keys (key_list); else *r_key_list = key_list; return err; } struct ssh_key_list { struct ssh_key_list *next; char *pubkey; /* Public key in SSH format. */ char *comment; }; static void release_ssh_key_list (struct ssh_key_list *key_list) { struct ssh_key_list *key; while (key_list) { key = key_list; key_list = key_list->next; xfree (key->pubkey); xfree (key->comment); xfree (key); } } static gpg_error_t ssh_authorized_keys (const char *user, struct ssh_key_list **r_ssh_key_list) { gpg_error_t err = 0; char *fname = NULL; estream_t fp = NULL; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; const char *fields[3]; struct ssh_key_list *ssh_key_list = NULL; struct ssh_key_list *ssh_key_prev = NULL; struct ssh_key_list *ssh_key = NULL; if (user) { char tilde_user[256]; snprintf (tilde_user, sizeof tilde_user, "~%s", user); fname = make_absfilename_try (tilde_user, ".ssh", "authorized_keys", NULL); } else fname = make_absfilename_try ("~", ".ssh", "authorized_keys", NULL); if (fname == NULL) return gpg_error (GPG_ERR_INV_NAME); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); xfree (fname); return err; } xfree (fname); maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; fields[2] = NULL; if (split_fields (line, fields, DIM (fields)) < 2) continue; /* Skip empty lines or line with only a field. */ if (*fields[0] == '#') continue; /* Skip comments. */ ssh_key = xtrycalloc (1, sizeof *ssh_key); if (!ssh_key) { err = gpg_error_from_syserror (); release_ssh_key_list (ssh_key_list); goto leave; } ssh_key->pubkey = strdup (fields[1]); ssh_key->comment = strdup (fields[2]); if (ssh_key_list) ssh_key_prev->next = ssh_key; else ssh_key_list = ssh_key; ssh_key_prev = ssh_key; } *r_ssh_key_list = ssh_key_list; leave: xfree (line); es_fclose (fp); return err; } static gpg_error_t ga_filter_by_authorized_keys (const char *user, struct ga_key_list **r_key_list) { gpg_error_t err; struct ga_key_list *cur = *r_key_list; struct ga_key_list *key_list = NULL; struct ga_key_list *prev = NULL; struct ssh_key_list *ssh_key_list = NULL; err = ssh_authorized_keys (user, &ssh_key_list); if (err) return err; if (ssh_key_list == NULL) return gpg_error (GPG_ERR_NOT_FOUND); while (cur) { struct ssh_key_list *skl = ssh_key_list; while (skl) if (!strncmp (cur->pubkey, skl->pubkey, cur->pubkey_len)) break; else skl = skl->next; /* valid? */ if (skl) { if (key_list) prev->next = cur; else key_list = cur; cur->comment = skl->comment; skl->comment = NULL; prev = cur; cur = cur->next; } else { struct ga_key_list *k = cur; cur = cur->next; xfree (k->pubkey); xfree (k); } } if (prev && prev->next) prev->next = NULL; release_ssh_key_list (ssh_key_list); *r_key_list = key_list; return 0; } static void ga_release_auth_keys (struct ga_key_list *key_list) { struct ga_key_list *key; while (key_list) { key = key_list; key_list = key_list->next; xfree (key->pubkey); xfree (key); } } static int getpin (const char *comment, const char *info, char *buf, size_t *r_len) { int rc = 0; char line[ASSUAN_LINELENGTH]; const char *fields[2]; (void)info; if (comment) { int msg_len = 29 + strlen (comment); fprintf (stdout, "P %d\n", msg_len); fprintf (stdout, "Please input PIN for KEY (%s): \n", comment); fflush (stdout); } else { fputs ("P 18\n", stdout); fputs ("Please input PIN: \n", stdout); fflush (stdout); } fgets (line, ASSUAN_LINELENGTH, stdin); if (split_fields (line, fields, DIM (fields)) < DIM (fields)) rc = GPG_ERR_PROTOCOL_VIOLATION; else if (strcmp (fields[0], "p") != 0) rc = GPG_ERR_CANCELED; if (!fgets (line, ASSUAN_LINELENGTH, stdin)) rc = GPG_ERR_PROTOCOL_VIOLATION; if (!rc) { size_t len = strlen (line); /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; len++; /* Include last '\0' in the data. */ if (len > *r_len) rc = GPG_ERR_BUFFER_TOO_SHORT; else memcpy (buf, line, len); *r_len = len; } return rc; } static gpg_error_t scd_pkauth (assuan_context_t ctx, const char *keygrip) { char line[ASSUAN_LINELENGTH]; gpg_error_t err; snprintf (line, DIM(line), "PKAUTH --challenge-response %s", keygrip); err = assuan_transact (ctx, line, NULL, NULL, inq_needpin, ctx, NULL, NULL); return err; } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 4002cc185..24518d105 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,4254 +1,4254 @@ /* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019--2022 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif /*HAVE_LIBREADLINE*/ #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/exechelp.h" #include "../common/ttyio.h" #include "../common/server-help.h" #include "../common/openpgpdefs.h" #include "../common/tlv.h" #include "../common/comopt.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define HISTORYNAME ".gpg-card_history" /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oNoAutostart, oAgentProgram, oDisplay, oTTYname, oTTYtype, oXauthority, oLCctype, oLCmessages, oNoKeyLookup, oNoHistory, oChUid, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", "use --no-key-lookup for \"list\""), ARGPARSE_s_n (oNoHistory,"no-history", "do not use the command history file"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* An object to create lists of labels and keyrefs. */ struct keyinfolabel_s { const char *label; const char *keyref; }; typedef struct keyinfolabel_s *keyinfolabel_t; /* Helper for --chuid. */ static const char *changeuser; /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 /* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 #define OPENPGP_KDF_DATA_LENGTH_MAX 110 /* Local prototypes. */ static void show_keysize_warning (void); static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. Without a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: - gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + gnupg_set_status_fd (translate_sys2libc_fdstr (pargs->r.ret_str, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs->r.ret_str); break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; case oNoKeyLookup: opt.no_key_lookup = 1; break; case oNoHistory: opt.no_history = 1; break; case oChUid: changeuser = pargs->r.ret_str; break; default: pargs->err = 2; break; } } } /* gpg-card main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; char **command_list = NULL; int cmdidx; char *command; gnupg_reopen_std ("gpg-card"); gpgrt_set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", gpg_strerror (gpg_error_from_syserror ())); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ if (log_get_errorcount (0)) exit (2); /* Process common component options. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); if (parse_comopt (GNUPG_MODULE_NAME_CARD, opt.debug)) { gnupg_status_printf (STATUS_FAILURE, "option-parser %u", gpg_error (GPG_ERR_GENERAL)); exit(2); } if (comopt.no_autostart) opt.autostart = 0; /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.gpgsm_program) opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); /* Now build the list of commands. We guess the size of the array * by assuming each item is a complete command. Obviously this will * be rarely the case, but it is less code to allocate a possible * too large array. */ command_list = xcalloc (argc+1, sizeof *command_list); cmdidx = 0; command = NULL; while (argc) { for ( ; argc && strcmp (*argv, "--"); argc--, argv++) { if (!command) command = xstrdup (*argv); else { char *tmp = xstrconcat (command, " ", *argv, NULL); xfree (command); command = tmp; } } if (argc) { /* Skip the double dash. */ argc--; argv++; } if (command) { command_list[cmdidx++] = command; command = NULL; } } opt.interactive = !cmdidx; if (!opt.interactive) opt.no_history = 1; if (opt.interactive) { interactive_loop (); err = 0; } else { struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) { err = dispatch_command (info, command); if (err) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* This was a "quit". */ else if (command && !opt.quiet) log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); if (command_list) { for (cmdidx=0; command_list[cmdidx]; cmdidx++) xfree (command_list[cmdidx]); xfree (command_list); } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Return S or the string "[none]" if S is NULL. */ static GPGRT_INLINE const char * nullnone (const char *s) { return s? s: "[none]"; } /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on * success returns 0 and stores the number of bytes read at R_BUFLEN * and the address of a newly allocated buffer at R_BUFFER. A * complementary nul byte is always appended to the data but not * counted; this allows to pass NULL for R-BUFFER and consider the * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { gpg_error_t err; estream_t fp; char *data; int n; *r_buffer = NULL; if (r_buflen) *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } data = xtrymalloc (MAX_GET_DATA_FROM_FILE); if (!data) { err = gpg_error_from_syserror (); log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); es_fclose (fp); return err; } n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { err = gpg_error_from_syserror (); tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (data); return err; } data[n] = 0; *r_buffer = data; if (r_buflen) *r_buflen = n; return 0; } /* Fixup the ENODEV error from scdaemon which we may see after * removing a card due to scdaemon scanning for readers with cards. * We also map the CAERD REMOVED error to the more useful CARD_NOT * PRESENT. */ static gpg_error_t fixup_scd_errors (gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); return err; } /* Set the card removed flag from INFO depending on ERR. This does * not clear the flag. */ static gpg_error_t maybe_set_card_removed (card_info_t info, gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) info->card_removed = 1; return err; } /* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on * success. */ static gpg_error_t put_data_to_file (const char *fname, const void *buffer, size_t length) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "wb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (length && es_fwrite (buffer, length, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); es_fclose (fp); return err; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); return err; } return 0; } /* Return a malloced string with the number opf the menu PROMPT. * Control-D is mapped to "Q". */ static char * get_selection (const char *prompt) { char *answer; tty_printf ("\n"); tty_printf ("%s", prompt); tty_printf ("\n"); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); if (*answer == CONTROL_D) strcpy (answer, "q"); return answer; } /* Simply prints TEXT to the output. Returns 0 as a convenience. * This is a separate function so that it can be extended to run * less(1) or so. The extra arguments are int values terminated by a * 0 to indicate card application types supported with this command. * If none are given (just the final 0), this is a general * command. */ static gpg_error_t print_help (const char *text, ...) { estream_t fp; va_list arg_ptr; int value; int any = 0; fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) tty_fprintf (fp, "[Supported by: "); tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; } /* Print an (OpenPGP) fingerprint. */ static void print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { int i; if (fpr) { for (i=0; i < fprlen ; i++, fpr++) tty_fprintf (fp, "%02X", *fpr); } else tty_fprintf (fp, " [none]"); tty_fprintf (fp, "\n"); } /* Print the keygrip GRP. */ static void print_keygrip (estream_t fp, const unsigned char *grp) { int i; for (i=0; i < 20 ; i++, grp++) tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } /* Print a string but avoid printing control characters. */ static void print_string (estream_t fp, const char *text, const char *name) { tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ if (name && *name) { if (fp) print_utf8_buffer2 (fp, name, strlen (name), '\n'); else tty_print_utf8_string2 (NULL, name, strlen (name), 0); } else tty_fprintf (fp, _("[not set]")); tty_fprintf (fp, "\n"); } /* Print an ISO formatted name or "[not set]". */ static void print_isoname (estream_t fp, const char *name) { if (name && *name) { char *p, *given, *buf; buf = xstrdup (name); given = strstr (buf, "<<"); for (p=buf; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; if (fp) print_utf8_buffer2 (fp, given, strlen (given), '\n'); else tty_print_utf8_string2 (NULL, given, strlen (given), 0); if (*buf) tty_fprintf (fp, " "); } if (fp) print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); else tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); xfree (buf); } else { tty_fprintf (fp, _("[not set]")); } tty_fprintf (fp, "\n"); } /* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int mem_is_zero (const char *mem, unsigned int memlen) { int i; for (i=0; i < memlen && !mem[i]; i++) ; return (i == memlen); } /* Helper to list a single keyref. LABEL_KEYREF is a fallback key * reference if no info is available; it may be NULL. */ static void list_one_kinfo (card_info_t info, key_info_t kinfo, const char *label_keyref, estream_t fp, int no_key_lookup, int create_shadow) { gpg_error_t err; key_info_t firstkinfo = info->kinfo; keyblock_t keyblock = NULL; keyblock_t kb; pubkey_t pubkey; userid_t uid; key_info_t ki; const char *s; gcry_sexp_t s_pkey; int any; if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); goto leave; } print_keygrip (fp, kinfo->grip); tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); if (kinfo->usage) { any = 0; tty_fprintf (fp, " ("); if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) { tty_fprintf (fp, "sign"); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_CERT)) { tty_fprintf (fp, "%scert", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) { tty_fprintf (fp, "%sauth", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) { tty_fprintf (fp, "%sencr", any?",":""); any=1; } tty_fprintf (fp, ")"); } tty_fprintf (fp, "\n"); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); if (!(err = scd_readkey (kinfo->keyref, create_shadow, &s_pkey))) { char *tmp = pubkey_algo_string (s_pkey, NULL); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (tmp)); xfree (tmp); gcry_sexp_release (s_pkey); s_pkey = NULL; } else { maybe_set_card_removed (info, err); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } if (kinfo->fprlen && kinfo->created) { tty_fprintf (fp, " stored fpr .: "); print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } if (no_key_lookup) err = 0; else err = get_matching_keys (kinfo->grip, (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), &keyblock); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) tty_fprintf (fp, " used for ...: [%s]\n", gpg_strerror (err)); goto leave; } for (kb = keyblock; kb; kb = kb->next) { tty_fprintf (fp, " used for ...: %s\n", kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); pubkey = kb->keys; if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) { /* If this is not the primary key print the primary * key's fingerprint or a reference to it. */ tty_fprintf (fp, " main key .: "); for (ki=firstkinfo; ki; ki = ki->next) if (pubkey->grip_valid && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) break; if (ki) { /* Fixme: Replace mapping by a table lookup. */ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) s = "this"; else if (!strcmp (ki->keyref, "OPENPGP.1")) s = "Signature key"; else if (!strcmp (ki->keyref, "OPENPGP.2")) s = "Encryption key"; else if (!strcmp (ki->keyref, "OPENPGP.3")) s = "Authentication key"; else s = NULL; if (s) tty_fprintf (fp, "<%s>\n", s); else tty_fprintf (fp, "\n", ki->keyref); } else /* Print the primary key as fallback. */ print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); } if (kb->protocol == GNUPG_PROTOCOL_OPENPGP || kb->protocol == GNUPG_PROTOCOL_CMS) { /* Find the primary or subkey of that key. */ for (; pubkey; pubkey = pubkey->next) if (pubkey->grip_valid && !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) break; if (pubkey) { tty_fprintf (fp, " fpr ......: "); print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); tty_fprintf (fp, " created ..: %s\n", isotimestamp (pubkey->created)); } } for (uid = kb->uids; uid; uid = uid->next) { print_string (fp, " user id ..: ", uid->value); } } } else { tty_fprintf (fp, " [none]\n"); if (label_keyref) tty_fprintf (fp, " keyref .....: %s\n", label_keyref); if (kinfo) tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } leave: release_keyblock (keyblock); } /* List all keyinfo in INFO using the list of LABELS. */ static void list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp, int no_key_lookup, int create_shadow) { key_info_t kinfo; int idx, i, j; /* Print the keyinfo. We first print those we known and then all * remaining item. */ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) kinfo->xflag = 0; if (labels) { for (idx=0; labels[idx].label; idx++) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); list_one_kinfo (info, kinfo, labels[idx].keyref, fp, no_key_lookup, create_shadow); if (kinfo) kinfo->xflag = 1; } } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { if (kinfo->xflag) continue; tty_fprintf (fp, "Key %s", kinfo->keyref); for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1) tty_fprintf (fp, j? ".":" "); tty_fprintf (fp, ":"); list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup, create_shadow); } } static void list_retry_counter (card_info_t info, estream_t fp) { const char *s; int i; if (info->chvlabels) tty_fprintf (fp, "PIN labels .......: %s\n", info->chvlabels); tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo) && i < info->nchvinfo; i++) { if (info->chvinfo[i] >= 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -4: s = "[nullpin]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); } /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "Signature key ....:", "OPENPGP.1" }, { "Encryption key....:", "OPENPGP.2" }, { "Authentication key:", "OPENPGP.3" }, { NULL, NULL } }; if (info->apptype != APP_TYPE_OPENPGP) { tty_fprintf (fp, "invalid OpenPGP card\n"); return; } tty_fprintf (fp, "Name of cardholder: "); print_isoname (fp, info->disp_name); print_string (fp, "Language prefs ...: ", info->disp_lang); tty_fprintf (fp, "Salutation .......: %s\n", info->disp_sex == 1? _("Mr."): info->disp_sex == 2? _("Ms.") : ""); print_string (fp, "URL of public key : ", info->pubkey_url); print_string (fp, "Login data .......: ", info->login_data); if (info->private_do[0]) print_string (fp, "Private DO 1 .....: ", info->private_do[0]); if (info->private_do[1]) print_string (fp, "Private DO 2 .....: ", info->private_do[1]); if (info->private_do[2]) print_string (fp, "Private DO 3 .....: ", info->private_do[2]); if (info->private_do[3]) print_string (fp, "Private DO 4 .....: ", info->private_do[3]); if (info->cafpr1len) { tty_fprintf (fp, "CA fingerprint %d .:", 1); print_shax_fpr (fp, info->cafpr1, info->cafpr1len); } if (info->cafpr2len) { tty_fprintf (fp, "CA fingerprint %d .:", 2); print_shax_fpr (fp, info->cafpr2, info->cafpr2len); } if (info->cafpr3len) { tty_fprintf (fp, "CA fingerprint %d .:", 3); print_shax_fpr (fp, info->cafpr3, info->cafpr3len); } tty_fprintf (fp, "Signature PIN ....: %s\n", info->chv1_cached? _("not forced"): _("forced")); tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); list_retry_counter (info, fp); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); tty_fprintf (fp, "Capabilities .....:"); if (info->extcap.ki) tty_fprintf (fp, " key-import"); if (info->extcap.aac) tty_fprintf (fp, " algo-change"); if (info->extcap.bt) tty_fprintf (fp, " button"); if (info->extcap.sm) tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo)); if (info->extcap.private_dos) tty_fprintf (fp, " priv-data"); tty_fprintf (fp, "\n"); if (info->extcap.kdf) { tty_fprintf (fp, "KDF setting ......: %s\n", info->kdf_do_enabled ? "on" : "off"); } if (info->extcap.bt) { tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off"); } list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PIV card specific data. */ static void list_piv (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "PIV authentication:", "PIV.9A" }, { "Card authenticat. :", "PIV.9E" }, { "Digital signature :", "PIV.9C" }, { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; if (info->chvusage[0] || info->chvusage[1]) { tty_fprintf (fp, "PIN usage policy .:"); if ((info->chvusage[0] & 0x40)) tty_fprintf (fp, " app-pin"); if ((info->chvusage[0] & 0x20)) tty_fprintf (fp, " global-pin"); if ((info->chvusage[0] & 0x10)) tty_fprintf (fp, " occ"); if ((info->chvusage[0] & 0x08)) tty_fprintf (fp, " vci"); if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) tty_fprintf (fp, " pairing"); if (info->chvusage[1] == 0x10) tty_fprintf (fp, " primary:card"); else if (info->chvusage[1] == 0x20) tty_fprintf (fp, " primary:global"); tty_fprintf (fp, "\n"); } list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List Netkey card specific data. */ static void list_nks (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PKCS#15 card specific data. */ static void list_p15 (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } static void print_a_version (estream_t fp, const char *prefix, unsigned int value) { unsigned int a, b, c, d; a = ((value >> 24) & 0xff); b = ((value >> 16) & 0xff); c = ((value >> 8) & 0xff); d = ((value ) & 0xff); if (a) tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); else if (b) tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); else if (c) tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); else tty_fprintf (fp, "%s %u\n", prefix, d); } /* Print all available information about the current card. With * NO_KEY_LOOKUP the sometimes expensive listing of all matching * OpenPGP and X.509 keys is not done */ static void list_card (card_info_t info, int no_key_lookup, int create_shadow) { estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", nullnone (info->reader)); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); if (info->cardversion) print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", nullnone (info->serialno)); tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); if (info->appversion) print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); if (info->manufacturer_name && info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s (%x)\n", info->manufacturer_name, info->manufacturer_id); else if (info->manufacturer_name && !info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name); else if (info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id); switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_PIV: list_piv (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_NKS: list_nks (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_P15: list_p15 (info, fp, no_key_lookup, create_shadow); break; default: break; } } /* Helper for cmd_list. */ static void print_card_list (estream_t fp, card_info_t info, strlist_t cards, int only_current) { int count; strlist_t sl; size_t snlen; int star; const char *s; for (count = 0, sl = cards; sl; sl = sl->next, count++) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); star = (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)); } else star = 0; if (!only_current || star) tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d); } } /* The LIST command. This also updates INFO if needed. */ static gpg_error_t cmd_list (card_info_t info, char *argstr) { gpg_error_t err; int opt_cards, opt_apps, opt_info, opt_reread, opt_no_key_lookup; int opt_shadow; strlist_t cards = NULL; strlist_t sl; estream_t fp = opt.interactive? NULL : es_stdout; const char *cardsn = NULL; char *appstr = NULL; int count; int need_learn = 0; if (!info) return print_help ("LIST [--cards] [--apps] [--info] [--reread] [--shadow]" " [--no-key-lookup] [N] [APP]\n\n" "Show the content of the current card.\n" "With N given select and list the N-th card;\n" "with APP also given select that application.\n" "To select an APP on the current card use '-' for N.\n" "The S/N of the card may be used instead of N.\n" " --cards list available cards\n" " --apps list additional card applications\n" " --info select a card and prints its s/n\n" " --reread read infos from PCKS#15 cards again\n" " --shadow create shadow keys for all card keys\n" " --no-key-lookup do not list matching OpenPGP or X.509 keys\n" , 0); opt_cards = has_leading_option (argstr, "--cards"); opt_apps = has_leading_option (argstr, "--apps"); opt_info = has_leading_option (argstr, "--info"); opt_reread = has_leading_option (argstr, "--reread"); opt_shadow = has_leading_option (argstr, "--shadow"); opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup"); argstr = skip_options (argstr); if (opt_shadow) opt_no_key_lookup = 1; if (opt.no_key_lookup) opt_no_key_lookup = 1; if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1))) { if (*argstr == '-' && (argstr[1] || spacep (argstr+1))) argstr++; /* Keep current card. */ else { cardsn = argstr; while (hexdigitp (argstr)) argstr++; if (*argstr && !spacep (argstr)) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (*argstr) *argstr++ = 0; } while (spacep (argstr)) argstr++; if (*argstr) { appstr = argstr; while (*argstr && !spacep (argstr)) argstr++; while (spacep (argstr)) argstr++; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } } } else if (*argstr) { /* First argument needs to be a digit. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (!info->serialno || info->need_sn_cmd) { /* This is probably the first call or was explicitly requested. * We need to send a SERIALNO command to scdaemon so that our * session knows all cards. */ err = scd_serialno (NULL, NULL); if (err) goto leave; info->need_sn_cmd = 0; need_learn = 1; } if (opt_cards || opt_apps) { /* Note that with option --apps CARDS is here the list of all * apps. Format is "SERIALNO APPNAME {APPNAME}". We print the * card number in the first column. */ if (opt_apps) err = scd_applist (&cards, opt_cards); else err = scd_cardlist (&cards); if (err) goto leave; print_card_list (fp, info, cards, 0); } else { if (cardsn) { int i, cardno; err = scd_cardlist (&cards); if (err) goto leave; /* Switch to the requested card. */ for (i=0; digitp (cardsn+i); i++) ; if (i && i < 4 && !cardsn[i]) { /* Looks like an index into the card list. */ cardno = atoi (cardsn); for (count = 0, sl = cards; sl; sl = sl->next, count++) if (count == cardno) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } else /* S/N of card specified. */ { for (sl = cards; sl; sl = sl->next) if (!ascii_strcasecmp (sl->d, cardsn)) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } err = scd_switchcard (sl->d); need_learn = 1; } else /* show app list. */ { err = scd_applist (&cards, 1); if (err) goto leave; } if (appstr && *appstr) { /* Switch to the requested app. */ err = scd_switchapp (appstr); if (err) goto leave; need_learn = 1; } if (need_learn) err = scd_learn (info, opt_reread); else err = 0; if (err) ; else if (opt_info) print_card_list (fp, info, cards, 1); else { size_t snlen; const char *s; /* First get the list of active cards and check whether the * current card is still in the list. If not the card has * been removed. Note that during the listing the card * remove state might also be detected but only if an access * to the scdaemon is required; it is anyway better to test * that before starting a listing. */ free_strlist (cards); err = scd_cardlist (&cards); if (err) goto leave; for (sl = cards; sl; sl = sl->next) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); if (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)) break; } } if (!sl) { info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); goto leave; } list_card (info, opt_no_key_lookup, opt_shadow); } } leave: free_strlist (cards); return err; } /* The VERIFY command. */ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err, err2; const char *pinref; if (!info) return print_help ("verify [chvid]", 0); if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = info->serialno; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else return gpg_error (GPG_ERR_MISSING_VALUE); err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); /* In any case update the CHV status, so that the next "list" shows * the correct retry counter values. */ err2 = scd_getattr ("CHV-STATUS", info); return err ? err : err2; } static gpg_error_t cmd_authenticate (card_info_t info, char *argstr) { gpg_error_t err; int opt_setkey; int opt_raw; char *string = NULL; char *key = NULL; size_t keylen; if (!info) return print_help ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" "Perform a mutual authentication either by reading the key\n" "from FILE or by taking it from the command line. Without\n" "the option --raw the key is expected to be hex encoded.\n" "To install a new administration key --setkey is used; this\n" "requires a prior authentication with the old key.", APP_TYPE_PIV, 0); if (info->apptype != APP_TYPE_PIV) { log_info ("Note: This is a PIV only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } opt_setkey = has_leading_option (argstr, "--setkey"); opt_raw = has_leading_option (argstr, "--raw"); argstr = skip_options (argstr); if (*argstr == '<') /* Read key from a file. */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &string, NULL); if (err) goto leave; } if (opt_raw) { key = string? string : xstrdup (argstr); string = NULL; keylen = strlen (key); } else { key = hex_to_buffer (string? string: argstr, &keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } } err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); leave: if (key) { wipememory (key, keylen); xfree (key); } xfree (string); return err; } /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) { char *name; int i; for (;;) { name = tty_get (prompt); trim_spaces (name); tty_kill_prompt (); if (!*name || *name == CONTROL_D) { if (*name == CONTROL_D) tty_fprintf (NULL, "\n"); xfree (name); return NULL; } for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) ; /* The name must be in Latin-1 and not UTF-8 - lacking the code * to ensure this we restrict it to ASCII. */ if (name[i]) tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); else if (strchr (name, '<')) tty_printf (_("Error: The \"<\" character may not be used.\n")); else if (strstr (name, " ")) tty_printf (_("Error: Double spaces are not allowed.\n")); else return name; xfree (name); } } /* The NAME command. */ static gpg_error_t cmd_name (card_info_t info, const char *argstr) { gpg_error_t err; char *surname, *givenname; char *isoname, *p; if (!info) return print_help ("name [--clear]\n\n" "Set the name field of an OpenPGP card. With --clear the stored\n" "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } again: if (!strcmp (argstr, "--clear")) isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ else { surname = ask_one_name (_("Cardholder's surname: ")); givenname = ask_one_name (_("Cardholder's given name: ")); if (!surname || !givenname || (!*surname && !*givenname)) { xfree (surname); xfree (givenname); return gpg_error (GPG_ERR_CANCELED); } isoname = xstrconcat (surname, "<<", givenname, NULL); xfree (surname); xfree (givenname); for (p=isoname; *p; p++) if (*p == ' ') *p = '<'; if (strlen (isoname) > 39 ) { log_info (_("Error: Combined name too long " "(limit is %d characters).\n"), 39); xfree (isoname); goto again; } } err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); xfree (isoname); return err; } static gpg_error_t cmd_url (card_info_t info, const char *argstr) { gpg_error_t err; char *url; if (!info) return print_help ("URL [--clear]\n\n" "Set the URL data object. That data object can be used by\n" "the FETCH command to retrieve the full public key. The\n" "option --clear deletes the content of that data object.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!strcmp (argstr, "--clear")) url = xstrdup (" "); /* No real way to clear; set to space instead. */ else { url = tty_get (_("URL to retrieve public key: ")); trim_spaces (url); tty_kill_prompt (); if (!*url || *url == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr ("PUBKEY-URL", url, strlen (url)); leave: xfree (url); return err; } /* Fetch the key from the URL given on the card or try to get it from * the default keyserver. */ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; key_info_t kinfo; if (!info) return print_help ("FETCH\n\n" "Retrieve a key using the URL data object or if that is missing\n" "using the fingerprint.", APP_TYPE_OPENPGP, 0); if (info->pubkey_url && *info->pubkey_url) { /* strlist_t sl = NULL; */ /* add_to_strlist (&sl, info.pubkey_url); */ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else err = gpg_error (GPG_ERR_NO_DATA); return err; } static gpg_error_t cmd_login (card_info_t info, char *argstr) { gpg_error_t err; char *data; size_t datalen; int use_default_pin; if (!info) return print_help ("LOGIN [--clear|--use-default-pin] [< FILE]\n\n" "Set the login data object. If FILE is given the data is\n" "is read from that file. This allows for binary data.\n" "The option --clear deletes the login data. --use-default-pin\n" "tells the card to always use the default PIN (\"123456\").", APP_TYPE_OPENPGP, 0); use_default_pin = has_leading_option (argstr, "--use-default-pin"); argstr = skip_options (argstr); if (!strcmp (argstr, "--clear")) { data = xstrdup (" "); /* kludge. */ datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { data = tty_get (_("Login data (account name): ")); trim_spaces (data); tty_kill_prompt (); if ((!*data && !use_default_pin) || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } datalen = strlen (data); } if (use_default_pin) { char *tmpdata = xmalloc (datalen + 5); memcpy (tmpdata, data, datalen); memcpy (tmpdata+datalen, "\n\x14" "F=3", 5); xfree (data); data = tmpdata; datalen += 5; } err = scd_setattr ("LOGIN-DATA", data, datalen); leave: xfree (data); return err; } static gpg_error_t cmd_lang (card_info_t info, const char *argstr) { gpg_error_t err; char *data, *p; if (!info) return print_help ("LANG [--clear]\n\n" "Change the language info for the card. This info can be used\n" "by applications for a personalized greeting. Up to 4 two-digit\n" "language identifiers can be entered as a preference. The option\n" "--clear removes all identifiers. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) data = xstrdup (" "); /* Note that we need two spaces here. */ else { again: data = tty_get (_("Language preferences: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (strlen (data) > 8 || (strlen (data) & 1)) { log_info (_("Error: invalid length of preference string.\n")); xfree (data); goto again; } for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) ; if (*p) { log_info (_("Error: invalid characters in preference string.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-LANG", data, strlen (data)); leave: xfree (data); return err; } static gpg_error_t cmd_salut (card_info_t info, const char *argstr) { gpg_error_t err; char *data = NULL; const char *str; if (!info) return print_help ("SALUT [--clear]\n\n" "Change the salutation info for the card. This info can be used\n" "by applications for a personalized greeting. The option --clear\n" "removes this data object. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); again: if (!strcmp (argstr, "--clear")) str = "9"; else { data = tty_get (_("Salutation (M = Mr., F = Ms., or space): ")); trim_spaces (data); tty_kill_prompt (); if (*data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!*data) str = "9"; else if ((*data == 'M' || *data == 'm') && !data[1]) str = "1"; else if ((*data == 'F' || *data == 'f') && !data[1]) str = "2"; else { tty_printf (_("Error: invalid response.\n")); xfree (data); data = NULL; goto again; } } err = scd_setattr ("DISP-SEX", str, 1); leave: xfree (data); return err; } static gpg_error_t cmd_cafpr (card_info_t info, char *argstr) { gpg_error_t err; char *data = NULL; const char *s; int i, c; unsigned char fpr[32]; int fprlen; int fprno; int opt_clear = 0; if (!info) return print_help ("CAFPR [--clear] N\n\n" "Change the CA fingerprint number N. N must be in the\n" "range 1 to 3. The option --clear clears the specified\n" "CA fingerprint N or all of them if N is 0 or not given.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { fprno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else fprno = 0; if (opt_clear && !fprno) ; /* Okay: clear all fprs. */ else if (fprno < 1 || fprno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } again: if (opt_clear) { memset (fpr, 0, 20); fprlen = 20; } else { xfree (data); data = tty_get (_("CA fingerprint: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } for (i=0, s=data; i < sizeof fpr && *s; ) { while (spacep(s)) s++; if (*s == ':') s++; while (spacep(s)) s++; c = hextobyte (s); if (c == -1) break; fpr[i++] = c; s += 2; } fprlen = i; if ((fprlen != 20 && fprlen != 32) || *s) { log_error (_("Error: invalid formatted fingerprint.\n")); goto again; } } if (!fprno) { log_assert (opt_clear); err = scd_setattr ("CA-FPR-1", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-2", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-3", fpr, fprlen); } else err = scd_setattr (fprno==1?"CA-FPR-1": fprno==2?"CA-FPR-2": fprno==3?"CA-FPR-3":"x", fpr, fprlen); leave: xfree (data); return err; } static gpg_error_t cmd_privatedo (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *do_name = NULL; char *data = NULL; size_t datalen; int do_no; if (!info) return print_help ("PRIVATEDO [--clear] N [< FILE]\n\n" "Change the private data object N. N must be in the\n" "range 1 to 4. If FILE is given the data is is read\n" "from that file. The option --clear clears the data.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { do_no = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else do_no = 0; if (do_no < 1 || do_no > 4) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_name = xasprintf ("PRIVATE-DO-%d", do_no); if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else if (*argstr) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } else { data = tty_get (_("Private DO data: ")); trim_spaces (data); tty_kill_prompt (); datalen = strlen (data); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr (do_name, data, datalen); leave: xfree (do_name); xfree (data); return err; } static gpg_error_t cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; int opt_openpgp; char *certref_buffer = NULL; char *certref; char *data = NULL; size_t datalen; estream_t key = NULL; if (!info) return print_help ("WRITECERT CERTREF '<' FILE\n" "WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n" "WRITECERT --clear CERTREF\n\n" "Write a certificate to the card under the id CERTREF.\n" "The option --clear removes the certificate from the card.\n" "The option --openpgp expects an OpenPGP keyblock and stores\n" "it encapsulated in a CMS container; the keyblock is taken\n" "from FILE or directly from the OpenPGP key with FPR", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } else /* Upcase the certref; prepend cardtype if needed. */ { if (!strchr (certref, '.')) certref_buffer = xstrconcat (app_type_string (info->apptype), ".", certref, NULL); else certref_buffer = xstrdup (certref); ascii_strupr (certref_buffer); certref = certref_buffer; } if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) { struct b64state b64; err = b64dec_start (&b64, ""); if (!err) err = b64dec_proc (&b64, data, datalen, &datalen); if (!err) err = b64dec_finish (&b64); if (err) goto leave; } } else if (opt_openpgp && *argstr) { err = get_minimal_openpgp_key (&key, argstr); if (err) goto leave; if (es_fclose_snatch (key, (void*)&data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_openpgp && !opt_clear) { tlv_builder_t tb; void *tmpder; size_t tmpderlen; tb = tlv_builder_new (0); if (!tb) { err = gpg_error_from_syserror (); goto leave; } tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10); tlv_builder_add_tag (tb, CLASS_CONTEXT, 0); tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen); tlv_builder_add_end (tb); tlv_builder_add_end (tb); err = tlv_builder_finalize (tb, &tmpder, &tmpderlen); if (err) goto leave; xfree (data); data = tmpder; datalen = tmpderlen; } err = scd_writecert (certref, data, datalen); leave: es_fclose (key); xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; char *certref_buffer = NULL; char *certref; void *data = NULL; size_t datalen, dataoff; const char *fname; int opt_openpgp; if (!info) return print_help ("READCERT [--openpgp] CERTREF > FILE\n\n" "Read the certificate for key CERTREF and store it in FILE.\n" "With option \"--openpgp\" an OpenPGP keyblock is expected\n" "and stored in FILE.\n", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; fname = argstr; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } dataoff = 0; err = scd_readcert (certref, &data, &datalen); if (err) goto leave; if (opt_openpgp) { /* Check whether DATA contains an OpenPGP keyblock and put only * this into FILE. If the data is something different, return * an error. */ const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; p = data; n = datalen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) goto not_openpgp; /* Does not start with a sequence. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) goto not_openpgp; /* No Object ID. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ if (objlen != 10 || memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen)) goto not_openpgp; /* Wrong Object ID. */ p += objlen; n -= objlen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_CONTEXT && tag == 0 && cons)) goto not_openpgp; /* Not a [0] context tag. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons)) goto not_openpgp; /* Not an octet string. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ dataoff = p - (const unsigned char*)data; datalen = objlen; } err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen); goto leave; not_openpgp: err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_writekey (card_info_t info, char *argstr) { gpg_error_t err; int opt_force; const char *argv[2]; int argc; char *keyref_buffer = NULL; const char *keyref; const char *keygrip; if (!info) return print_help ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" "Write a private key object identified by KEYGRIP to slot KEYREF.\n" "Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_force = has_leading_option (argstr, "--force"); argstr = skip_options (argstr); argc = split_fields (argstr, argv, DIM (argv)); if (argc < 2) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Upcase the keyref; prepend cardtype if needed. */ keyref = argv[0]; if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; /* Get the keygrip. */ keygrip = argv[1]; if (strlen (keygrip) != 40 && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) { log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writekey (keyref, opt_force, keygrip); leave: xfree (keyref_buffer); return err; } static gpg_error_t cmd_forcesig (card_info_t info) { gpg_error_t err; int newstate; if (!info) return print_help ("FORCESIG\n\n" "Toggle the forcesig flag of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } newstate = !info->chv1_cached; err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); if (err) goto leave; /* Read it back to be sure we have the right toggle state the next * time. */ err = scd_getattr ("CHV-STATUS", info); leave: return err; } /* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) { gpg_error_t err = 0; *forced_chv1 = !info->chv1_cached; if (*forced_chv1) { /* Switch off the forced mode so that during key generation we * don't get bothered with PIN queries for each self-signature. */ err = scd_setattr ("CHV-STATUS-1", "\x01", 1); if (err) { log_error ("error clearing forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = -1; /* Not changed. */ goto leave; } } /* Check the PIN now, so that we won't get asked later for each * binding signature. */ err = scd_checkpin (info->serialno); if (err) log_error ("error checking the PIN: %s\n", gpg_strerror (err)); leave: return err; } /* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { gpg_error_t err; /* Note the possible values stored at FORCED_CHV1: * 0 - forcesig was not enabled. * 1 - forcesig was enabled - enable it again. * -1 - We have not changed anything. */ if (*forced_chv1 == 1) { /* Switch back to forced state. */ err = scd_setattr ("CHV-STATUS-1", "", 1); if (err) log_error ("error setting forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = 0; } } /* Ask whether existing keys shall be overwritten. With NULL used for * KINFO it will ask for all keys, other wise for the given key. */ static gpg_error_t ask_replace_keys (key_info_t kinfo) { gpg_error_t err; char *answer; tty_printf ("\n"); if (kinfo) log_info (_("Note: key %s is already stored on the card!\n"), kinfo->keyref); else log_info (_("Note: Keys are already stored on the card!\n")); tty_printf ("\n"); if (kinfo) answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref); else answer = tty_get (_("Replace existing keys? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; xfree (answer); return err; } /* Implementation of cmd_generate for OpenPGP cards to generate all * standard keys at once. */ static gpg_error_t generate_all_openpgp_card_keys (card_info_t info, char **algos) { gpg_error_t err; int forced_chv1 = -1; int want_backup; char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; if (info->extcap.ki) { xfree (answer); answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else want_backup = 0; kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) ) { err = ask_replace_keys (NULL); if (err) goto leave; } /* If no displayed name has been set, we assume that this is a fresh * card and print a hint about the default PINs. */ if (!info->disp_name || !*info->disp_name) { tty_printf ("\n"); tty_printf (_("Please note that the factory settings of the PINs are\n" " PIN = '%s' Admin PIN = '%s'\n" "You should change them using the command --change-pin\n"), OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); tty_printf ("\n"); } err = check_pin_for_key_operation (info, &forced_chv1); if (err) goto leave; (void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */ /* FIXME: We need to divert to a function which spawns gpg which * will then create the key. This also requires new features in * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ (void)want_backup; err = scd_genkey ("OPENPGP.1", 1, NULL, NULL); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Create a single key. This is a helper for cmd_generate. */ static gpg_error_t generate_key (card_info_t info, const char *keyref, int force, const char *algo) { gpg_error_t err; key_info_t kinfo; if (info->apptype == APP_TYPE_OPENPGP) { kinfo = find_kinfo (info, keyref); if (!kinfo) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } if (!force && kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen)) { err = ask_replace_keys (NULL); if (err) goto leave; force = 1; } } err = scd_genkey (keyref, force, algo, NULL); leave: return err; } static gpg_error_t cmd_generate (card_info_t info, char *argstr) { static char * const valid_algos[] = { "rsa2048", "rsa3072", "rsa4096", "", "nistp256", "nistp384", "nistp521", "", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "", "ed25519", "cv25519", NULL }; gpg_error_t err; int opt_force; char *p; char **opt_algo = NULL; /* Malloced. */ char *keyref_buffer = NULL; /* Malloced. */ char *keyref; /* Points into argstr or keyref_buffer. */ int i, j; if (!info) return print_help ("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n" "Create a new key on a card.\n" "Use --force to overwrite an existing key.\n" "Use \"help\" for ALGO to get a list of known algorithms.\n" "For OpenPGP cards several algos may be given.\n" "Note that the OpenPGP key generation is done interactively\n" "unless a single ALGO or KEYREF are given.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); opt_force = has_leading_option (argstr, "--force"); err = get_option_value (argstr, "--algo", &p); if (err) goto leave; if (p) { opt_algo = strtokenize (p, "+"); if (!opt_algo) { err = gpg_error_from_syserror (); xfree (p); goto leave; } xfree (p); } argstr = skip_options (argstr); keyref = argstr; if ((argstr = strchr (keyref, ' '))) { *argstr++ = 0; trim_spaces (keyref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = keyref + strlen (keyref); if (!*keyref) keyref = NULL; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_algo) { /* opt_algo is an array of algos. */ for (i=0; opt_algo[i]; i++) { for (j=0; valid_algos[j]; j++) if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i])) break; if (!valid_algos[j]) { int lf = 1; if (!ascii_strcasecmp (opt_algo[i], "help")) log_info ("Known algorithms:\n"); else { log_info ("Invalid algorithm '%s' given. Use one of:\n", opt_algo[i]); err = gpg_error (GPG_ERR_PUBKEY_ALGO); } for (i=0; valid_algos[i]; i++) { if (!*valid_algos[i]) lf = 1; else if (lf) { lf = 0; log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } else log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } log_printf ("\n"); show_keysize_warning (); goto leave; } } } /* Upcase the keyref; if it misses the cardtype, prepend it. */ if (keyref) { if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; } /* Special checks. */ if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) && info->cardversion >= 0x040200 && info->cardversion < 0x040305) { log_error ("On-chip key generation on this YubiKey has been blocked.\n"); log_info ("Please see for details\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP && !keyref && (!opt_algo || (opt_algo[0] && opt_algo[1]))) { /* With no algo requested or more than one algo requested and no * keyref given we create all keys. */ if (opt_force || keyref) log_info ("Note: OpenPGP key generation is interactive.\n"); err = generate_all_openpgp_card_keys (info, opt_algo); } else if (!keyref) err = gpg_error (GPG_ERR_INV_ID); else if (opt_algo && opt_algo[0] && opt_algo[1]) { log_error ("only one algorithm expected as value for --algo.\n"); err = gpg_error (GPG_ERR_INV_ARG); } else err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL); if (!err) { err = scd_learn (info, 0); if (err) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); } leave: xfree (opt_algo); xfree (keyref_buffer); return err; } /* Change a PIN. */ static gpg_error_t cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err = 0; char *answer = NULL; const char *pinref = NULL; int reset_mode = 0; int nullpin = 0; int menu_used = 0; if (!info) return print_help ("PASSWD [--reset|--nullpin] [PINREF]\n\n" "Change or unblock the PINs. Note that in interactive mode\n" "and without a PINREF a menu is presented for certain cards;\n" "in non-interactive and without a PINREF a default value is\n" "used for these cards. The option --reset is used with TCOS\n" "cards to reset the PIN using the PUK or vice versa; --nullpin\n" "is used for these cards to set the initial PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (has_option (argstr, "--reset")) reset_mode = 1; else if (has_option (argstr, "--nullpin")) nullpin = 1; argstr = skip_options (argstr); /* If --reset or --nullpin has been given we force non-interactive mode. */ if (*argstr || reset_mode || nullpin) { pinref = argstr; if (!*pinref) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } } else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - unblock and set new a PIN\n" "3 - change the Admin PIN\n" "4 - set the Reset Code\n" "Q - quit\n"); if (strlen (answer) != 1) continue; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "OPENPGP.1"; else if (*answer == '2') { pinref = "OPENPGP.1"; reset_mode = 1; } else if (*answer == '3') pinref = "OPENPGP.3"; else if (*answer == '4') { pinref = "OPENPGP.2"; reset_mode = 1; } } } else if (info->apptype == APP_TYPE_OPENPGP) pinref = "OPENPGP.1"; else if (opt.interactive && info->apptype == APP_TYPE_PIV) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - change the PUK\n" "3 - change the Global PIN\n" "Q - quit\n"); if (strlen (answer) != 1) ; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "PIV.80"; else if (*answer == '2') pinref = "PIV.81"; else if (*answer == '3') pinref = "PIV.00"; } } else if (opt.interactive && info->apptype == APP_TYPE_NKS) { int for_qualified = 0; menu_used = 1; log_assert (DIM (info->chvinfo) >= 4); /* If there is a qualified signature use a menu to select * between standard PIN and QES PINs. */ if (info->chvinfo[2] != -2 || info->chvinfo[3] != -2) { for (;;) { xfree (answer); answer = get_selection (" 1 - Standard PIN/PUK\n" " 2 - PIN/PUK for qualified signature\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) break; else if (!strcmp (answer, "2")) { for_qualified = 1; break; } } } if (info->chvinfo[for_qualified? 2 : 0] == -4) { while (!pinref) { xfree (answer); answer = get_selection ("The NullPIN is still active on this card.\n" "You need to choose and set a PIN first.\n" "\n" " 1 - Set your PIN\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; nullpin = 1; } } } else { while (!pinref) { xfree (answer); answer = get_selection (" 1 - change PIN\n" " 2 - reset PIN\n" " 3 - change PUK\n" " 4 - reset PUK\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; } else if (!strcmp (answer, "2")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; reset_mode = 1; } else if (!strcmp (answer, "3")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; } else if (!strcmp (answer, "4")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; reset_mode = 1; } } } } else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = scd_change_pin (pinref, reset_mode, nullpin); if (err) { if (!opt.interactive && !menu_used && !opt.verbose) ; else if (gpg_err_code (err) == GPG_ERR_CANCELED && gpg_err_source (err) == GPG_ERR_SOURCE_PINENTRY) log_info ("%s\n", gpg_strerror (err)); else if (!ascii_strcasecmp (pinref, "PIV.81")) log_error ("Error changing the PUK.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_error ("Error unblocking the PIN.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_error ("Error setting the Reset Code.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_error ("Error changing the Admin PIN.\n"); else if (reset_mode) log_error ("Error resetting the PIN.\n"); else log_error ("Error changing the PIN.\n"); } else { if (!opt.interactive && !opt.verbose) ; else if (!ascii_strcasecmp (pinref, "PIV.81")) log_info ("PUK changed.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_info ("PIN unblocked and new PIN set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_info ("Reset Code set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_info ("Admin PIN changed.\n"); else if (reset_mode) log_info ("PIN reset.\n"); else log_info ("PIN changed.\n"); /* Update the CHV status. */ err = scd_getattr ("CHV-STATUS", info); } leave: xfree (answer); return err; } static gpg_error_t cmd_unblock (card_info_t info) { gpg_error_t err = 0; if (!info) return print_help ("UNBLOCK\n\n" "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" "cards prior to version 2 can't use this; instead the PASSWD\n" "command can be used to set a new PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (info->apptype == APP_TYPE_OPENPGP) { if (!info->is_v2) { log_error (_("This command is only available for version 2 cards\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (!info->chvinfo[1]) { log_error (_("Reset Code not or not anymore available\n")); err = gpg_error (GPG_ERR_PIN_BLOCKED); } else { err = scd_change_pin ("OPENPGP.2", 0, 0); if (!err) log_info ("PIN changed.\n"); } } else if (info->apptype == APP_TYPE_PIV) { /* Unblock the Application PIN. */ err = scd_change_pin ("PIV.80", 1, 0); if (!err) log_info ("PIN unblocked and changed.\n"); } else { log_info ("Unblocking not supported for '%s'.\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } return err; } /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t cmd_factoryreset (card_info_t info) { gpg_error_t err; char *answer = NULL; int termstate = 0; int any_apdu = 0; int is_yubikey = 0; int locked = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" "Do a complete reset of some OpenPGP and PIV cards. This\n" "deletes all data and keys and resets the PINs to their default.\n" "This is mainly used by developers with scratch cards. Don't\n" "worry, you need to confirm before the command proceeds.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); /* We support the factory reset for most OpenPGP cards and Yubikeys * with the PIV application. */ if (info->apptype == APP_TYPE_OPENPGP) ; else if (info->apptype == APP_TYPE_PIV && info->cardtype && !strcmp (info->cardtype, "yubikey")) is_yubikey = 1; else return gpg_error (GPG_ERR_NOT_SUPPORTED); /* For an OpenPGP card the code below basically does the same what * this gpg-connect-agent script does: * * scd reset * scd serialno undefined * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 e6 00 00 * scd apdu 00 44 00 00 * scd reset * /echo Card has been reset to factory defaults * * For a PIV application on a Yubikey it merely issues the Yubikey * specific resset command. */ err = scd_learn (info, 0); if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) termstate = 1; else if (err) { log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); goto leave; } if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!termstate || is_yubikey) { if (!is_yubikey) { if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it * is not possible to select a card application in * termination state. */ log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } } tty_printf ("\n"); log_info (_("Note: This command destroys all keys stored on the card!\n")); tty_printf ("\n"); xfree (answer); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); trim_spaces (answer); if (*answer == CONTROL_D || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } xfree (answer); answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); tty_kill_prompt (); trim_spaces (answer); if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (is_yubikey) { /* If the PIV application is already selected, we only need to * send the special reset APDU after having blocked PIN and * PUK. Note that blocking the PUK is done using the * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff, NULL, NULL); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } else /* OpenPGP card. */ { any_apdu = 1; /* We need to select a card application before we can send * APDUs to the card without scdaemon doing anything on its * own. We then lock the connection so that other tools * (e.g. Kleopatra) don't try a new select. */ err = send_apdu ("lock", "locking connection ", 0, NULL, NULL); if (err) goto leave; locked = 1; err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, NULL, NULL); if (err) goto leave; /* Do some dummy verifies with wrong PINs to set the retry * counter to zero. We can't easily use the card version 2.1 * feature of presenting the admin PIN to allow the terminate * command because there is no machinery in scdaemon to catch * the verify command and ask for the PIN when the "APDU" * command is used. * Here, the length of dummy wrong PIN is 32-byte, also * supporting authentication with KDF DO. */ for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } } if (!is_yubikey) { any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ if (locked) err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); else err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; /* Then, connect the card again. */ err = scd_serialno (NULL, NULL); if (!err) info->need_sn_cmd = 0; leave: if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); /* FIXME: We need a better solution in the case that the card is * in a termination state, i.e. the card was removed before the * activate was sent. The best solution I found with v2.1 * Zeitcontrol card was to kill scdaemon and the issue this * sequence with gpg-connect-agent: * scd reset * scd serialno undefined * scd apdu 00A4040006D27600012401 (returns error) * scd apdu 00440000 * Then kill scdaemon again and issue: * scd reset * scd serialno openpgp */ } if (locked) send_apdu ("unlock", "unlocking connection ", 0, NULL, NULL); xfree (answer); return err; } /* Generate KDF data. This is a helper for cmd_kdfsetup. */ static gpg_error_t gen_kdf_data (unsigned char *data, int single_salt) { gpg_error_t err; const unsigned char h0[] = { 0x81, 0x01, 0x03, 0x82, 0x01, 0x08, 0x83, 0x04 }; const unsigned char h1[] = { 0x84, 0x08 }; const unsigned char h2[] = { 0x85, 0x08 }; const unsigned char h3[] = { 0x86, 0x08 }; const unsigned char h4[] = { 0x87, 0x20 }; const unsigned char h5[] = { 0x88, 0x20 }; unsigned char *p, *salt_user, *salt_admin; unsigned char s2k_char; unsigned int iterations; unsigned char count_4byte[4]; p = data; s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; count_4byte[2] = (iterations >> 8) & 0xff; count_4byte[3] = (iterations & 0xff); memcpy (p, h0, sizeof h0); p += sizeof h0; memcpy (p, count_4byte, sizeof count_4byte); p += sizeof count_4byte; memcpy (p, h1, sizeof h1); salt_user = (p += sizeof h1); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; if (single_salt) salt_admin = salt_user; else { memcpy (p, h2, sizeof h2); p += sizeof h2; gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; memcpy (p, h3, sizeof h3); salt_admin = (p += sizeof h3); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; } memcpy (p, h4, sizeof h4); p += sizeof h4; err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, strlen (OPENPGP_USER_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_user, 8, iterations, 32, p); p += 32; if (!err) { memcpy (p, h5, sizeof h5); p += sizeof h5; err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, strlen (OPENPGP_ADMIN_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_admin, 8, iterations, 32, p); } return err; } static gpg_error_t cmd_kdfsetup (card_info_t info, char *argstr) { gpg_error_t err; unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; int single = (*argstr != 0); if (!info) return print_help ("KDF-SETUP\n\n" "Prepare the OpenPGP card KDF feature for this card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!info->extcap.kdf) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gen_kdf_data (kdf_data, single); if (err) goto leave; err = scd_setattr ("KDF", kdf_data, single ? OPENPGP_KDF_DATA_LENGTH_MIN /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); if (err) goto leave; err = scd_getattr ("KDF", info); leave: return err; } static void show_keysize_warning (void) { static int shown; if (shown) return; shown = 1; tty_printf (_("Note: There is no guarantee that the card supports the requested\n" " key type or size. If the key generation does not succeed,\n" " please check the documentation of your card to see which\n" " key types and sizes are supported.\n") ); } static gpg_error_t cmd_uif (card_info_t info, char *argstr) { gpg_error_t err; int keyno; char name[50]; unsigned char data[2]; char *answer = NULL; int opt_yes; if (!info) return print_help ("UIF N [on|off|permanent]\n\n" "Change the User Interaction Flag. N must in the range 1 to 3.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (!info->extcap.bt) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } opt_yes = has_leading_option (argstr, "--yes"); argstr = skip_options (argstr); if (digitp (argstr)) { keyno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else keyno = 0; if (keyno < 1 || keyno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if ( !strcmp (argstr, "off") ) data[0] = 0x00; else if ( !strcmp (argstr, "on") ) data[0] = 0x01; else if ( !strcmp (argstr, "permanent") ) data[0] = 0x02; else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } data[1] = 0x20; log_assert (keyno - 1 < DIM(info->uif)); if (info->uif[keyno-1] == 2) { log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"), "permanent"); err = gpg_error (GPG_ERR_INV_STATE); goto leave; } if (data[0] == 0x02) { if (opt.interactive) { tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; } else if (!opt_yes) { log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); log_info (_("Please use \"uif --yes %d %s\"\n"), keyno, "permanent"); err = gpg_error (GPG_ERR_CANCELED); } else err = 0; if (err) goto leave; } snprintf (name, sizeof name, "UIF-%d", keyno); err = scd_setattr (name, data, 2); if (!err) /* Read all UIF attributes again. */ err = scd_getattr ("UIF", info); leave: xfree (answer); return err; } static gpg_error_t cmd_yubikey (card_info_t info, char *argstr) { gpg_error_t err, err2; estream_t fp = opt.interactive? NULL : es_stdout; const char *words[20]; int nwords; if (!info) return print_help ("YUBIKEY args\n\n" "Various commands pertaining to Yubikey tokens with being:\n" "\n" " LIST \n" "\n" "List supported and enabled applications.\n" "\n" " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" "\n" "Enable or disable the specified or all applications on the\n" "given interface.", 0); argstr = skip_options (argstr); if (!info->cardtype || strcmp (info->cardtype, "yubikey")) { log_info ("This command can only be used with Yubikeys.\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } nwords = split_fields (argstr, words, DIM (words)); if (nwords < 1) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Note that we always do a learn to get a chance to the card back * into a usable state. */ err = yubikey_commands (info, fp, nwords, words); err2 = scd_learn (info, 0); if (err2) log_error ("Error re-reading card: %s\n", gpg_strerror (err2)); leave: return err; } static gpg_error_t cmd_apdu (card_info_t info, char *argstr) { gpg_error_t err; estream_t fp = opt.interactive? NULL : es_stdout; int with_atr; int handle_more; const char *s; const char *exlenstr; int exlenstrlen; char *options = NULL; unsigned int sw; unsigned char *result = NULL; size_t i, j, resultlen; if (!info) return print_help ("APDU [--more] [--exlen[=N]] \n" "\n" "Send an APDU to the current card. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU.\n" "\n" "Using the option \"--more\" handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used.\n", 0); if (has_option (argstr, "--dump-atr")) with_atr = 2; else with_atr = has_option (argstr, "--atr"); handle_more = has_option (argstr, "--more"); exlenstr = has_option_name (argstr, "--exlen"); exlenstrlen = 0; if (exlenstr) { for (s=exlenstr; *s && !spacep (s); s++) exlenstrlen++; } argstr = skip_options (argstr); if (with_atr || handle_more || exlenstr) options = xasprintf ("%s%s%s%.*s", with_atr == 2? " --dump-atr": with_atr? " --data-atr":"", handle_more?" --more":"", exlenstr?" --exlen=":"", exlenstrlen, exlenstr?exlenstr:""); err = scd_apdu (argstr, options, &sw, &result, &resultlen); if (err) goto leave; if (!with_atr) { if (opt.interactive || opt.verbose) { char *p = scd_apdu_strerror (sw); log_info ("Statusword: 0x%04x (%s)\n", sw, p? p: "?"); xfree (p); } else log_info ("Statusword: 0x%04x\n", sw); } for (i=0; i < resultlen; ) { size_t save_i = i; tty_fprintf (fp, "D[%04X] ", (unsigned int)i); for (j=0; j < 16 ; j++, i++) { if (j == 8) tty_fprintf (fp, " "); if (i < resultlen) tty_fprintf (fp, " %02X", result[i]); else tty_fprintf (fp, " "); } tty_fprintf (fp, " "); i = save_i; for (j=0; j < 16; j++, i++) { unsigned int c = result[i]; if ( i >= resultlen ) tty_fprintf (fp, " "); else if (isascii (c) && isprint (c) && !iscntrl (c)) tty_fprintf (fp, "%c", c); else tty_fprintf (fp, "."); } tty_fprintf (fp, "\n"); } leave: xfree (result); xfree (options); return err; } static gpg_error_t cmd_gpg (card_info_t info, char *argstr, int use_gpgsm) { gpg_error_t err; char **argarray; ccparray_t ccp; const char **argv = NULL; gnupg_process_t proc; int i; if (!info) return print_help ("GPG[SM] \n" "\n" "Run gpg/gpgsm directly from this shell.\n", 0); /* Fixme: We need to write and use a version of strtokenize which * takes care of shell-style quoting. */ argarray = strtokenize (argstr, " \t\n\v"); if (!argarray) { err = gpg_error_from_syserror (); goto leave; } ccparray_init (&ccp, 0); for (i=0; argarray[i]; i++) ccparray_put (&ccp, argarray[i]); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (use_gpgsm? opt.gpgsm_program:opt.gpg_program, argv, (GNUPG_PROCESS_STDOUT_KEEP | GNUPG_PROCESS_STDERR_KEEP), NULL, NULL, &proc); if (!err) { err = gnupg_process_wait (proc, 1); gnupg_process_release (proc); } leave: xfree (argv); xfree (argarray); return err; } static gpg_error_t cmd_history (card_info_t info, char *argstr) { int opt_list, opt_clear; opt_list = has_option (argstr, "--list"); opt_clear = has_option (argstr, "--clear"); if (!info || !(opt_list || opt_clear)) return print_help ("HISTORY --list\n" " List the command history\n" "HISTORY --clear\n" " Clear the command history", 0); if (opt_list) tty_printf ("Sorry, history listing not yet possible\n"); if (opt_clear) tty_read_history (NULL, 0); return 0; } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY, cmdINVCMD }; static struct { const char *name; enum cmdids id; const char *desc; } cmds[] = { { "quit" , cmdQUIT, N_("quit this menu")}, { "q" , cmdQUIT, NULL }, { "bye" , cmdQUIT, NULL }, { "help" , cmdHELP, N_("show this help")}, { "?" , cmdHELP, NULL }, { "list" , cmdLIST, N_("list all available data")}, { "l" , cmdLIST, NULL }, { "name" , cmdNAME, N_("change card holder's name")}, { "url" , cmdURL, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN, N_("change the login name")}, { "lang" , cmdLANG, N_("change the language preferences")}, { "salutation",cmdSALUT, N_("change card holder's salutation")}, { "salut" , cmdSALUT, NULL }, { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, N_("generate new keys")}, { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTH, N_("authenticate to the card")}, { "auth" , cmdAUTH, NULL }, { "reset" , cmdRESET, N_("send a reset to the card daemon")}, { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, { "uif", cmdUIF, N_("change the User Interaction Flag")}, { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "gpg", cmdGPG, NULL}, { "gpgsm", cmdGPGSM, NULL}, { "apdu", cmdAPDU, NULL}, { "history", cmdHISTORY, N_("manage the command history")}, { NULL, cmdINVCMD, NULL } }; /* The command line command dispatcher. */ static gpg_error_t dispatch_command (card_info_t info, const char *orig_command) { gpg_error_t err = 0; enum cmdids cmd; /* The command. */ char *command; /* A malloced copy of ORIG_COMMAND. */ char *argstr; /* The argument as a string. */ int i; int ignore_error; if ((ignore_error = *orig_command == '-')) orig_command++; command = xstrdup (orig_command); argstr = NULL; if ((argstr = strchr (command, ' '))) { *argstr++ = 0; trim_spaces (command); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (command, cmds[i].name )) break; cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ /* Make sure we have valid strings for the args. They are allowed * to be modified and must thus point to a buffer. */ if (!argstr) argstr = command + strlen (command); /* For most commands we need to make sure that we have a card. */ if (!info) ; /* Help mode */ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD) && !info->initialized) { err = scd_learn (info, 0); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); goto leave; } } if (info) info->card_removed = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Stop processing.", 0); else { err = gpg_error (GPG_ERR_EOF); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) dispatch_command (NULL, argstr); else { es_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); es_printf ("Prefix a command with a dash to ignore its error.\n"); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */ case cmdINVCMD: default: log_error (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ leave: /* Return GPG_ERR_EOF only if its origin was "quit". */ es_fflush (es_stdout); if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) err = gpg_error (GPG_ERR_GENERAL); if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { err = fixup_scd_errors (err); if (ignore_error) { log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); err = 0; } else { log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } xfree (command); return err; } /* The interactive main loop. */ static void interactive_loop (void) { gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; char *historyname = NULL; /* In the interactive mode we do not want to print the program prefix. */ log_set_prefix (NULL, 0); if (!opt.no_history) { historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL); if (tty_read_history (historyname, 500)) log_info ("error reading '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); } for (;;) { if (help_arg) { /* Clear info to indicate helpmode */ info = NULL; } else if (!info) { /* Get out of help. */ info = &info_buffer; help_arg = NULL; redisplay = 0; } else if (redisplay) { err = cmd_list (info, ""); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); } else { tty_printf("\n"); redisplay = 0; } } if (!info) { /* Copy the pending help arg into our answer. Note that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; xfree (answer); answer = p; } else { do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); } argstr = NULL; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((argstr = strchr (answer,' '))) { *argstr++ = 0; trim_spaces (answer); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; } /* Make sure we have valid strings for the args. They are * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdHISTORY || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ if (!info) ; /* In help mode. */ else if (redisplay) { cmd = cmdLIST; } else if (!info->serialno) { /* Without a serial number most commands won't work. * Catch it here. */ if (cmd == cmdRESET || cmd == cmdLIST) info->need_sn_cmd = 1; else { tty_printf ("\n"); tty_printf ("Serial number missing\n"); continue; } } } if (info) info->card_removed = 0; err = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Leave this tool.", 0); else { tty_printf ("\n"); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) help_arg = argstr; /* Trigger help for a command. */ else { tty_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = cmd_history (info, argstr); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (gpg_err_code (err) == GPG_ERR_CANCELED) tty_fprintf (NULL, "\n"); else if (err) { const char *s = "?"; for (i=0; cmds[i].name; i++ ) if (cmd == cmds[i].id) { s = cmds[i].name; break; } err = fixup_scd_errors (err); log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } /* End of main menu loop. */ leave: if (historyname && tty_write_history (historyname)) log_info ("error writing '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); release_card_info (info); xfree (historyname); xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes * saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen(text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text. */ if (cmds[list_index++].desc && !strncmp (name, text, len)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. The support for help completion * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-pair-tool.c b/tools/gpg-pair-tool.c index cf9778838..069bd1668 100644 --- a/tools/gpg-pair-tool.c +++ b/tools/gpg-pair-tool.c @@ -1,1965 +1,1965 @@ /* gpg-pair-tool.c - The tool to run the pairing protocol. * Copyright (C) 2018 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* Protocol: * * Initiator Responder * | | * | COMMIT | * |-------------------->| * | | * | DHPART1 | * |<--------------------| * | | * | DHPART2 | * |-------------------->| * | | * | CONFIRM | * |<--------------------| * | | * * The initiator creates a keypair (PKi,SKi) and sends this COMMIT * message to the responder: * * 7 byte Magic, value: "GPG-pa1" * 1 byte MessageType, value 1 (COMMIT) * 8 byte SessionId, value: 8 random bytes * 1 byte Realm, value 1 * 2 byte reserved, value 0 * 5 byte ExpireTime, value: seconds since Epoch as an unsigned int. * 32 byte Hash(PKi) * * The initiator also needs to locally store the sessionid, the realm, * the expiration time, the keypair and a hash of the entire message * sent. * * The responder checks that the received message has not expired and * stores sessionid, realm, expiretime and the Hash(PKi). The * Responder then creates and locally stores its own keypair (PKr,SKr) * and sends the DHPART1 message back: * * 7 byte Magic, value: "GPG-pa1" * 1 byte MessageType, value 2 (DHPART1) * 8 byte SessionId from COMMIT message * 32 byte PKr * 32 byte Hash(Hash(COMMIT) || DHPART1[0..47]) * * Note that Hash(COMMIT) is the hash over the entire received COMMIT * message. DHPART1[0..47] are the first 48 bytes of the created * DHPART1 message. * * The Initiator receives the DHPART1 message and checks that the hash * matches. Although this hash is easily malleable it is later in the * protocol used to assert the integrity of all messages. The * Initiator then computes the shared master secret from its SKi and * the received PKr. Using this master secret several keys are * derived: * * - HMACi-key using the label "GPG-pa1-HMACi-key". * - SYMx-key using the label "GPG-pa1-SYMx-key" * * For details on the KDF see the implementation of the function kdf. * The master secret is stored securely in the local state. The * DHPART2 message is then created and send to the Responder: * * 7 byte Magic, value: "GPG-pa1" * 1 byte MessageType, value 3 (DHPART2) * 8 byte SessionId from COMMIT message * 32 byte PKi * 32 byte MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) * * The Responder receives the DHPART2 message and checks that the hash * of the received PKi matches the Hash(PKi) value as received earlier * with the COMMIT message. The Responder now also computes the * shared master secret from its SKr and the received PKi and derives * the keys: * * - HMACi-key using the label "GPG-pa1-HMACi-key". * - HMACr-key using the label "GPG-pa1-HMACr-key". * - SYMx-key using the label "GPG-pa1-SYMx-key" * - SAS using the label "GPG-pa1-SAS" * * With these keys the MAC from the received DHPART2 message is * checked. On success a SAS is displayed to the user and a CONFIRM * message send back: * * 7 byte Magic, value: "GPG-pa1" * 1 byte MessageType, value 4 (CONFIRM) * 8 byte SessionId from COMMIT message * 32 byte MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) * * The Initiator receives this CONFIRM message, gets the master shared * secret from its local state and derives the keys. It checks the * MAC in the received CONFIRM message and ask the user to enter * the SAS as displayed by the responder. Iff the SAS matches the * master key is flagged as confirmed and the Initiator may now use a * derived key to send encrypted data to the Responder. * * In case the Responder also needs to send encrypted data we need to * introduce another final message to tell the responder that the * Initiator validated the SAS. * * TODO: Encrypt the state files using a key stored in gpg-agent's cache. * */ #include #include #include #include #include #include #include #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/name-value.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oArmor = 'a', aInitiate = 400, aRespond = 401, aGet = 402, aCleanup = 403, oDebug = 500, oStatusFD, oHomedir, oSAS, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_c (aInitiate, "initiate", N_("initiate a pairing request")), ARGPARSE_c (aRespond, "respond", N_("respond to a pairing request")), ARGPARSE_c (aGet, "get", N_("return the keys")), ARGPARSE_c (aCleanup, "cleanup", N_("remove expired states etc.")), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_s (oSAS, "sas", N_("|SAS|the SAS as shown by the peer")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write the request to FILE")), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_end () }; /* We keep all global options in the structure OPT. */ static struct { int verbose; unsigned int debug; int quiet; int armor; const char *output; estream_t statusfp; unsigned int ttl; const char *sas; } opt; /* Debug values and macros. */ #define DBG_MESSAGE_VALUE 2 /* Debug the messages. */ #define DBG_CRYPTO_VALUE 4 /* Debug low level crypto. */ #define DBG_MEMORY_VALUE 32 /* Debug memory allocation stuff. */ #define DBG_MESSAGE (opt.debug & DBG_MESSAGE_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MESSAGE_VALUE, "message" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { 0, NULL } }; /* The directory name below the cache dir to store paring states. */ #define PAIRING_STATE_DIR "state" /* Message types. */ #define MSG_TYPE_COMMIT 1 #define MSG_TYPE_DHPART1 2 #define MSG_TYPE_DHPART2 3 #define MSG_TYPE_CONFIRM 4 /* Realm values. */ #define REALM_STANDARD 1 /* Local prototypes. */ static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...) GPGRT_ATTR_PRINTF(3,4); static void *hash_data (void *result, size_t resultsize, ...) GPGRT_ATTR_SENTINEL(0); static void *hmac_data (void *result, size_t resultsize, const unsigned char *key, size_t keylen, ...) GPGRT_ATTR_SENTINEL(0); static gpg_error_t command_initiate (void); static gpg_error_t command_respond (void); static gpg_error_t command_cleanup (void); static gpg_error_t command_get (const char *sessionidstr); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "LGPL-2.1-or-later"; break; case 11: p = "gpg-pair-tool"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-pair-tool [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-pair-tool [command] [options] [args]\n" "Client to run the pairing protocol\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), gpgrt_strusage (11), text); exit (2); } /* Set the status FD. */ static void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (opt.statusfp && opt.statusfp != es_stdout && opt.statusfp != es_stderr) es_fclose (opt.statusfp); opt.statusfp = NULL; if (fd == -1) return; if (fd == 1) opt.statusfp = es_stdout; else if (fd == 2) opt.statusfp = es_stderr; else opt.statusfp = es_fdopen (fd, "w"); if (!opt.statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ static void write_status (int no, const char *format, ...) { va_list arg_ptr; if (!opt.statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", opt.statusfp); es_fputs (get_status_string (no), opt.statusfp); if (format) { es_putc (' ', opt.statusfp); va_start (arg_ptr, format); es_vfprintf (opt.statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', opt.statusfp); } /* gpg-pair-tool main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs = { &argc, &argv }; enum cmd_and_opt_values cmd = 0; opt.ttl = 8*3600; /* Default to 8 hours. */ gnupg_reopen_std ("gpg-pair-tool"); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-pair-tool", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); /* Parse the command line. */ while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oArmor: opt.armor = 1; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oOutput: opt.output = pargs.r.ret_str; break; case oStatusFD: - set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + set_status_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oSAS: opt.sas = pargs.r.ret_str; break; case aInitiate: case aRespond: case aGet: case aCleanup: if (cmd && cmd != pargs.r_opt) log_error (_("conflicting commands\n")); else cmd = pargs.r_opt; break; default: pargs.err = ARGPARSE_PRINT_WARNING; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (("NOTE: '%s' is not considered an option\n"), argv[i]); } gpgrt_argparse (NULL, &pargs, NULL); /* Free internal memory. */ if (opt.sas) { if (strlen (opt.sas) != 11 || !digitp (opt.sas+0) || !digitp (opt.sas+1) || !digitp (opt.sas+2) || opt.sas[3] != '-' || !digitp (opt.sas+4) || !digitp (opt.sas+5) || !digitp (opt.sas+6) || opt.sas[7] != '-' || !digitp (opt.sas+8) || !digitp (opt.sas+9) || !digitp (opt.sas+10)) log_error ("invalid formatted SAS\n"); } /* Stop if any error, including ARGPARSE_PRINT_WARNING, occurred. */ if (log_get_errorcount (0)) exit (2); if (DBG_CRYPTO) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1|2); /* Now run the requested command. */ switch (cmd) { case aInitiate: if (argc) wrong_args ("--initiate"); err = command_initiate (); break; case aRespond: if (argc) wrong_args ("--respond"); err = command_respond (); break; case aGet: if (argc > 1) wrong_args ("--respond [sessionid]"); err = command_get (argc? *argv:NULL); break; case aCleanup: if (argc) wrong_args ("--cleanup"); err = command_cleanup (); break; default: gpgrt_usage (1); err = 0; break; } if (err) write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else write_status (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Wrapper around nvc_new which terminates in the error case. */ static nvc_t xnvc_new (void) { nvc_t c = nvc_new (); if (!c) log_fatal ("error creating NVC object: %s\n", gpg_strerror (gpg_error_from_syserror ())); return c; } /* Wrapper around nvc_set which terminates in the error case. */ static void xnvc_set (nvc_t nvc, const char *name, const char *value) { gpg_error_t err = nvc_set (nvc, name, value); if (err) log_fatal ("error updating NVC object: %s\n", gpg_strerror (err)); } /* Call vnc_set with (BUFFER, BUFLEN) converted to a hex string as * value. Terminates in the error case. */ static void xnvc_set_hex (nvc_t nvc, const char *name, const void *buffer, size_t buflen) { char *hex; hex = bin2hex (buffer, buflen, NULL); if (!hex) xoutofcore (); strlwr (hex); xnvc_set (nvc, name, hex); xfree (hex); } /* Call nvc_set with a value created from the string generated using * the printf style FORMAT. Terminates in the error case. */ static void xnvc_set_printf (nvc_t nvc, const char *name, const char *format, ...) { va_list arg_ptr; char *buffer; va_start (arg_ptr, format); if (gpgrt_vasprintf (&buffer, format, arg_ptr) < 0) log_fatal ("estream_asprintf failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); va_end (arg_ptr); xnvc_set (nvc, name, buffer); xfree (buffer); } /* Return the string for the first entry in NVC with NAME. If NAME is * missing, an empty string is returned. The returned string is a * pointer into NVC. */ static const char * xnvc_get_string (nvc_t nvc, const char *name) { nve_t item; if (!nvc) return ""; item = nvc_lookup (nvc, name); if (!item) return ""; return nve_value (item); } /* Return a string for MSGTYPE. */ const char * msgtypestr (int msgtype) { switch (msgtype) { case MSG_TYPE_COMMIT: return "Commit"; case MSG_TYPE_DHPART1: return "DHPart1"; case MSG_TYPE_DHPART2: return "DHPart2"; case MSG_TYPE_CONFIRM: return "Confirm"; } return "?"; } /* Private to {get,set}_session_id(). */ static struct { int initialized; unsigned char sessid[8]; } session_id; /* Return the 8 octet session. */ static unsigned char * get_session_id (void) { if (!session_id.initialized) { session_id.initialized = 1; gcry_create_nonce (session_id.sessid, sizeof session_id.sessid); } return session_id.sessid; } static void set_session_id (const void *sessid, size_t len) { log_assert (!session_id.initialized); if (len > sizeof session_id.sessid) len = sizeof session_id.sessid; memcpy (session_id.sessid, sessid, len); if (len < sizeof session_id.sessid) memset (session_id.sessid+len, 0, sizeof session_id.sessid - len); session_id.initialized = 1; } /* Return a string with the hexified session id. */ static const char * get_session_id_hex (void) { static char hexstr[16+1]; bin2hex (get_session_id (), 8, hexstr); strlwr (hexstr); return hexstr; } /* Return a fixed string with the directory used to store the state of * pairings. On error a diagnostic is printed but the file name is * returned anyway. It is expected that the expected failure of the * following open is responsible for error handling. */ static const char * get_pairing_statedir (void) { static char *fname; gpg_error_t err = 0; char *tmpstr; struct stat statbuf; if (fname) return fname; fname = make_filename (gnupg_homedir (), GNUPG_CACHE_DIR, NULL); if (gnupg_stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwx")) { err = gpg_error_from_syserror (); log_error (_("can't create directory '%s': %s\n"), fname, gpg_strerror (err) ); } else if (!opt.quiet) log_info (_("directory '%s' created\n"), fname); } tmpstr = make_filename (fname, PAIRING_STATE_DIR, NULL); xfree (fname); fname = tmpstr; if (gnupg_stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwx")) { if (!err) { err = gpg_error_from_syserror (); log_error (_("can't create directory '%s': %s\n"), fname, gpg_strerror (err) ); } } else if (!opt.quiet) log_info (_("directory '%s' created\n"), fname); } return fname; } /* Open the pairing state file. SESSIONID is a 8 byte buffer with the * session-id. If CREATE_FLAG is set the file is created and will * always return a valid stream. If CREATE_FLAG is not set the file * is opened for reading and writing. If the file does not exist NULL * is return; in all other error cases the process is terminated. If * R_FNAME is not NULL the name of the file is stored there and the * caller needs to free it. */ static estream_t open_pairing_state (const unsigned char *sessionid, int create_flag, char **r_fname) { gpg_error_t err; char *fname, *tmpstr; estream_t fp; /* The filename is the session id with a "pa1" suffix. Note that * the state dir may eventually be used for other purposes as well * and thus the suffix identifies that the file belongs to this * tool. We use lowercase file names for no real reason. */ tmpstr = bin2hex (sessionid, 8, NULL); if (!tmpstr) xoutofcore (); strlwr (tmpstr); fname = xstrconcat (tmpstr, ".pa1", NULL); xfree (tmpstr); tmpstr = make_filename (get_pairing_statedir (), fname, NULL); xfree (fname); fname = tmpstr; fp = es_fopen (fname, create_flag? "wbx,mode=-rw": "rb+,mode=-rw"); if (!fp) { err = gpg_error_from_syserror (); if (create_flag) { /* We should always be able to create a file. Also we use a * 64 bit session id, it is theoretically possible that such * a session already exists. However, that is rare enough * and thus the fatal error message should still be okay. */ log_fatal ("can't create '%s': %s\n", fname, gpg_strerror (err)); } else if (gpg_err_code (err) == GPG_ERR_ENOENT) { /* That is an expected error; return NULL. */ } else { log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err)); } } if (r_fname) *r_fname = fname; else xfree (fname); return fp; } /* Write the state to a possible new state file. */ static void write_state (nvc_t state, int create_flag) { gpg_error_t err; char *fname = NULL; estream_t fp; fp = open_pairing_state (get_session_id (), create_flag, &fname); log_assert (fp); err = nvc_write (state, fp); if (err) { es_fclose (fp); gnupg_remove (fname); log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); } /* If we did not create the file, we need to truncate the file. */ if (!create_flag && ftruncate (es_fileno (fp), es_ftello (fp))) { err = gpg_error_from_syserror (); log_fatal ("error truncating '%s': %s\n", fname, gpg_strerror (err)); } if (es_ferror (fp) || es_fclose (fp)) { err = gpg_error_from_syserror (); es_fclose (fp); gnupg_remove (fname); log_fatal ("error writing '%s': %s\n", fname, gpg_strerror (err)); } } /* Read the state into a newly allocated state object and store that * at R_STATE. If no state is available GPG_ERR_NOT_FOUND is returned * and as with all errors NULL is stored at R_STATE. SESSIONID is an * input with the 8 session id. */ static gpg_error_t read_state (nvc_t *r_state) { gpg_error_t err; char *fname = NULL; estream_t fp; nvc_t state = NULL; nve_t item; const char *value; unsigned long expire; *r_state = NULL; fp = open_pairing_state (get_session_id (), 0, &fname); if (!fp) return gpg_error (GPG_ERR_NOT_FOUND); err = nvc_parse (&state, NULL, fp); if (err) { log_info ("failed to parse state file '%s': %s\n", fname, gpg_strerror (err)); goto leave; } /* Check whether the state already expired. */ item = nvc_lookup (state, "Expires:"); if (!item) { log_info ("invalid state file '%s': %s\n", fname, "field 'expire' not found"); goto leave; } value = nve_value (item); if (!value || !(expire = strtoul (value, NULL, 10))) { log_info ("invalid state file '%s': %s\n", fname, "field 'expire' has an invalid value"); goto leave; } if (expire <= gnupg_get_time ()) { es_fclose (fp); fp = NULL; if (gnupg_remove (fname)) { err = gpg_error_from_syserror (); log_info ("failed to delete state file '%s': %s\n", fname, gpg_strerror (err)); } else if (opt.verbose) log_info ("state file '%s' deleted\n", fname); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } *r_state = state; state = NULL; leave: nvc_release (state); es_fclose (fp); return err; } /* Send (MSG,MSGLEN) to the output device. */ static void send_message (const unsigned char *msg, size_t msglen) { gpg_error_t err; if (opt.verbose) log_info ("session %s: sending %s message\n", get_session_id_hex (), msgtypestr (msg[7])); if (DBG_MESSAGE) log_printhex (msg, msglen, "send msg(%s):", msgtypestr (msg[7])); /* FIXME: For now only stdout. */ if (opt.armor) { gpgrt_b64state_t state; state = gpgrt_b64enc_start (es_stdout, ""); if (!state) log_fatal ("error setting up base64 encoder: %s\n", gpg_strerror (gpg_error_from_syserror ())); err = gpgrt_b64enc_write (state, msg, msglen); if (!err) err = gpgrt_b64enc_finish (state); if (err) log_fatal ("error writing base64 to stdout: %s\n", gpg_strerror (err)); } else { if (es_fwrite (msg, msglen, 1, es_stdout) != 1) log_fatal ("error writing to stdout: %s\n", gpg_strerror (gpg_error_from_syserror ())); } es_fputc ('\n', es_stdout); } /* Read a message from stdin and store it at the address (R_MSG, * R_MSGLEN). This function detects armoring and removes it. On * error NULL is stored at R_MSG, a diagnostic printed and an error * code returned. The returned message has a proper message type and * an appropriate length. The message type is stored at R_MSGTYPE and * if a state is available it is stored at R_STATE. */ static gpg_error_t read_message (unsigned char **r_msg, size_t *r_msglen, int *r_msgtype, nvc_t *r_state) { gpg_error_t err; unsigned char msg[128]; /* max msg size is 80 but 107 with base64. */ size_t msglen; size_t reqlen; *r_msg = NULL; *r_state = NULL; es_setvbuf (es_stdin, NULL, _IONBF, 0); es_set_binary (es_stdin); if (es_read (es_stdin, msg, sizeof msg, &msglen)) { err = gpg_error_from_syserror (); log_error ("error reading from message: %s\n", gpg_strerror (err)); return err; } if (msglen > 4 && !memcmp (msg, "R1BH", 4)) { /* This is base64 of the first 3 bytes. */ gpgrt_b64state_t state = gpgrt_b64dec_start (NULL); if (!state) log_fatal ("error setting up base64 decoder: %s\n", gpg_strerror (gpg_error_from_syserror ())); err = gpgrt_b64dec_proc (state, msg, msglen, &msglen); gpgrt_b64dec_finish (state); if (err) { log_error ("error decoding message: %s\n", gpg_strerror (err)); return err; } } if (msglen < 16 || memcmp (msg, "GPG-pa1", 7)) { log_error ("error parsing message: %s\n", msglen? "invalid header":"empty message"); return gpg_error (GPG_ERR_INV_RESPONSE); } switch (msg[7]) { case MSG_TYPE_COMMIT: reqlen = 56; break; case MSG_TYPE_DHPART1: reqlen = 80; break; case MSG_TYPE_DHPART2: reqlen = 80; break; case MSG_TYPE_CONFIRM: reqlen = 48; break; default: log_error ("error parsing message: %s\n", "invalid message type"); return gpg_error (GPG_ERR_INV_RESPONSE); } if (msglen < reqlen) { log_error ("error parsing message: %s\n", "message too short"); return gpg_error (GPG_ERR_INV_RESPONSE); } if (DBG_MESSAGE) log_printhex (msg, msglen, "recv msg(%s):", msgtypestr (msg[7])); /* Note that we ignore any garbage at the end of a message. */ msglen = reqlen; set_session_id (msg+8, 8); if (opt.verbose) log_info ("session %s: received %s message\n", get_session_id_hex (), msgtypestr (msg[7])); /* Read the state. */ err = read_state (r_state); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) return err; *r_msg = xmalloc (msglen); memcpy (*r_msg, msg, msglen); *r_msglen = msglen; *r_msgtype = msg[7]; return err; } /* Display the Short Authentication String (SAS). If WAIT is true the * function waits until the user has entered the SAS as seen at the * peer. * * To construct the SAS we take the 4 most significant octets of HASH, * interpret them as a 32 bit big endian unsigned integer, divide that * integer by 10^9 and take the remainder. The remainder is displayed * as 3 groups of 3 decimal digits delimited by a hyphens. This gives * a search space of close to 2^30 and is still easy to compare. */ static gpg_error_t display_sas (const unsigned char *hash, size_t hashlen, int wait) { gpg_error_t err = 0; unsigned long sas = 0; char sasbuf[12]; log_assert (hashlen >= 4); sas |= (unsigned long)hash[20] << 24; sas |= (unsigned long)hash[21] << 16; sas |= (unsigned long)hash[22] << 8; sas |= (unsigned long)hash[23]; sas %= 1000000000ul; snprintf (sasbuf, sizeof sasbuf, "%09lu", sas); memmove (sasbuf+8, sasbuf+6, 3); memmove (sasbuf+4, sasbuf+3, 3); sasbuf[3] = sasbuf[7] = '-'; sasbuf[11] = 0; if (wait) log_info ("Please check the SAS:\n"); else log_info ("Please note the SAS:\n"); log_info ("\n"); log_info (" %s\n", sasbuf); log_info ("\n"); if (wait) { if (!opt.sas || strcmp (sasbuf, opt.sas)) err = gpg_error (GPG_ERR_NOT_CONFIRMED); else log_info ("SAS confirmed\n"); } if (err) log_info ("checking SAS failed: %s\n", gpg_strerror (err)); return err; } static gpg_error_t create_dh_keypair (unsigned char *dh_secret, size_t dh_secret_len, unsigned char *dh_public, size_t dh_public_len) { gpg_error_t err; unsigned char *p; /* We need a temporary buffer for the public key. Check the length * for the later memcpy. */ if (dh_public_len < 32 || dh_secret_len < 32) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); if (gcry_ecc_get_algo_keylen (GCRY_ECC_CURVE25519) > dh_public_len) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); p = gcry_random_bytes (32, GCRY_VERY_STRONG_RANDOM); if (!p) return gpg_error_from_syserror (); memcpy (dh_secret, p, 32); xfree (p); err = gcry_ecc_mul_point (GCRY_ECC_CURVE25519, dh_public, dh_secret, NULL); if (err) return err; if (DBG_CRYPTO) { log_printhex (dh_secret, 32, "DH secret:"); log_printhex (dh_public, 32, "DH public:"); } return 0; } /* SHA256 the data given as varargs tuples of (const void*, size_t) * and store the result in RESULT. The end of the list is indicated * by a NULL element in a tuple. RESULTLEN gives the length of the * RESULT buffer which must be at least 32. Note that the second item * of the tuple is the length and it is a size_t. */ static void * hash_data (void *result, size_t resultsize, ...) { va_list arg_ptr; gpg_error_t err; gcry_md_hd_t hd; const void *data; size_t datalen; log_assert (resultsize >= 32); err = gcry_md_open (&hd, GCRY_MD_SHA256, 0); if (err) log_fatal ("error creating a Hash handle: %s\n", gpg_strerror (err)); /* log_printhex ("", 0, "Hash-256:"); */ va_start (arg_ptr, resultsize); while ((data = va_arg (arg_ptr, const void *))) { datalen = va_arg (arg_ptr, size_t); /* log_printhex (data, datalen, " data:"); */ gcry_md_write (hd, data, datalen); } va_end (arg_ptr); memcpy (result, gcry_md_read (hd, 0), 32); /* log_printhex (result, 32, " result:"); */ gcry_md_close (hd); return result; } /* HMAC-SHA256 the data given as varargs tuples of (const void*, * size_t) using (KEYLEN,KEY) and store the result in RESULT. The end * of the list is indicated by a NULL element in a tuple. RESULTLEN * gives the length of the RESULT buffer which must be at least 32. * Note that the second item of the tuple is the length and it is a * size_t. */ static void * hmac_data (void *result, size_t resultsize, const unsigned char *key, size_t keylen, ...) { va_list arg_ptr; gpg_error_t err; gcry_mac_hd_t hd; const void *data; size_t datalen; log_assert (resultsize >= 32); err = gcry_mac_open (&hd, GCRY_MAC_HMAC_SHA256, 0, NULL); if (err) log_fatal ("error creating a MAC handle: %s\n", gpg_strerror (err)); err = gcry_mac_setkey (hd, key, keylen); if (err) log_fatal ("error setting the MAC key: %s\n", gpg_strerror (err)); /* log_printhex (key, keylen, "HMAC-key:"); */ va_start (arg_ptr, keylen); while ((data = va_arg (arg_ptr, const void *))) { datalen = va_arg (arg_ptr, size_t); /* log_printhex (data, datalen, " data:"); */ err = gcry_mac_write (hd, data, datalen); if (err) log_fatal ("error writing to the MAC handle: %s\n", gpg_strerror (err)); } va_end (arg_ptr); err = gcry_mac_read (hd, result, &resultsize); if (err || resultsize != 32) log_fatal ("error reading MAC value: %s\n", gpg_strerror (err)); /* log_printhex (result, resultsize, " result:"); */ gcry_mac_close (hd); return result; } /* Key derivation function: * * FIXME(doc) */ static void kdf (unsigned char *result, size_t resultlen, const unsigned char *master, size_t masterlen, const unsigned char *sessionid, size_t sessionidlen, const unsigned char *expire, size_t expirelen, const char *label) { log_assert (masterlen == 32 && sessionidlen == 8 && expirelen == 5); log_assert (*label); log_assert (resultlen == 32); hmac_data (result, resultlen, master, masterlen, "\x00\x00\x00\x01", (size_t)4, /* Counter=1*/ label, strlen (label) + 1, /* Label, 0x00 */ sessionid, sessionidlen, /* Context */ expire, expirelen, /* Context */ "\x00\x00\x01\x00", (size_t)4, /* L=256 */ NULL); } static gpg_error_t compute_master_secret (unsigned char *master, size_t masterlen, const unsigned char *sk_a, size_t sk_a_len, const unsigned char *pk_b, size_t pk_b_len) { gpg_error_t err; log_assert (masterlen == 32); log_assert (sk_a_len == 32); log_assert (pk_b_len == 32); err = gcry_ecc_mul_point (GCRY_ECC_CURVE25519, master, sk_a, pk_b); if (err) log_error ("error computing DH: %s\n", gpg_strerror (err)); return err; } /* We are the Initiator: Create the commit message. This function * sends the COMMIT message and writes STATE. */ static gpg_error_t make_msg_commit (nvc_t state) { gpg_error_t err; uint64_t now, expire; unsigned char secret[32]; unsigned char public[32]; unsigned char *newmsg; size_t newmsglen; unsigned char tmphash[32]; err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); if (err) log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); now = gnupg_get_time (); expire = now + opt.ttl; newmsglen = 7+1+8+1+2+5+32; newmsg = xmalloc (newmsglen); memcpy (newmsg+0, "GPG-pa1", 7); newmsg[7] = MSG_TYPE_COMMIT; memcpy (newmsg+8, get_session_id (), 8); newmsg[16] = REALM_STANDARD; newmsg[17] = 0; newmsg[18] = 0; newmsg[19] = expire >> 32; newmsg[20] = expire >> 24; newmsg[21] = expire >> 16; newmsg[22] = expire >> 8; newmsg[23] = expire; gcry_md_hash_buffer (GCRY_MD_SHA256, newmsg+24, public, 32); /* Create the state file. */ xnvc_set (state, "State:", "Commit-sent"); xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); xnvc_set_hex (state, "DH-PKi:", public, 32); xnvc_set_hex (state, "DH-SKi:", secret, 32); gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); xnvc_set_hex (state, "Hash-Commit:", tmphash, 32); /* Write the state. Note that we need to create it. The state * updating should in theory be done atomically with send_message. * However, we can't assure that the message will actually be * delivered and thus it doesn't matter whether we have an already * update state when we later fail in send_message. */ write_state (state, 1); /* Write the message. */ send_message (newmsg, newmsglen); xfree (newmsg); return err; } /* We are the Responder: Process a commit message in (MSG,MSGLEN) * which has already been validated to have a correct header and * message type. Sends the DHPart1 message and writes STATE. */ static gpg_error_t proc_msg_commit (nvc_t state, const unsigned char *msg, size_t msglen) { gpg_error_t err; uint64_t now, expire; unsigned char tmphash[32]; unsigned char secret[32]; unsigned char public[32]; unsigned char *newmsg = NULL; size_t newmsglen; log_assert (msglen >= 56); now = gnupg_get_time (); /* Check that the message has not expired. */ expire = (uint64_t)msg[19] << 32; expire |= (uint64_t)msg[20] << 24; expire |= (uint64_t)msg[21] << 16; expire |= (uint64_t)msg[22] << 8; expire |= (uint64_t)msg[23]; if (expire < now) { log_error ("received %s message is too old\n", msgtypestr (MSG_TYPE_COMMIT)); err = gpg_error (GPG_ERR_TOO_OLD); goto leave; } /* Create the response. */ err = create_dh_keypair (secret, sizeof secret, public, sizeof public ); if (err) { log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); goto leave; } newmsglen = 7+1+8+32+32; newmsg = xmalloc (newmsglen); memcpy (newmsg+0, "GPG-pa1", 7); newmsg[7] = MSG_TYPE_DHPART1; memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ memcpy (newmsg+16, public, 32); /* PKr */ /* Hash(Hash(Commit) || DHPart1[0..47]) */ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); hash_data (newmsg+48, 32, tmphash, sizeof tmphash, newmsg, (size_t)48, NULL); /* Update the state. */ xnvc_set (state, "State:", "DHPart1-sent"); xnvc_set_printf (state, "Created:", "%llu", (unsigned long long)now); xnvc_set_printf (state, "Expires:", "%llu", (unsigned long long)expire); xnvc_set_hex (state, "Hash-PKi:", msg+24, 32); xnvc_set_hex (state, "DH-PKr:", public, 32); xnvc_set_hex (state, "DH-SKr:", secret, 32); gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); xnvc_set_hex (state, "Hash-DHPart1:", tmphash, 32); /* Write the state. Note that we need to create it. */ write_state (state, 1); /* Write the message. */ send_message (newmsg, newmsglen); leave: xfree (newmsg); return err; } /* We are the Initiator: Process a DHPART1 message in (MSG,MSGLEN) * which has already been validated to have a correct header and * message type. Sends the DHPart2 message and writes STATE. */ static gpg_error_t proc_msg_dhpart1 (nvc_t state, const unsigned char *msg, size_t msglen) { gpg_error_t err; unsigned char hash[32]; unsigned char tmphash[32]; unsigned char pki[32]; unsigned char pkr[32]; unsigned char ski[32]; unsigned char master[32]; uint64_t expire; unsigned char expirebuf[5]; unsigned char hmacikey[32]; unsigned char symxkey[32]; unsigned char *newmsg = NULL; size_t newmsglen; log_assert (msglen >= 80); /* Check that the message includes the Hash(Commit). */ if (hex2bin (xnvc_get_string (state, "Hash-Commit:"), hash, sizeof hash) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'Hash-Commit' in our state file\n"); goto leave; } hash_data (tmphash, 32, hash, sizeof hash, msg, (size_t)48, NULL); if (memcmp (msg+48, tmphash, 32)) { err = gpg_error (GPG_ERR_BAD_DATA); log_error ("manipulation of received %s message detected: %s\n", msgtypestr (MSG_TYPE_DHPART1), "Bad Hash"); goto leave; } /* Check that the received PKr is different from our PKi and copy * PKr into PKR. */ if (hex2bin (xnvc_get_string (state, "DH-PKi:"), pki, sizeof pki) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'DH-PKi' in our state file\n"); goto leave; } if (!memcmp (msg+16, pki, 32)) { /* This can only happen if the state file leaked to the * responder. */ err = gpg_error (GPG_ERR_BAD_DATA); log_error ("received our own public key PKi instead of PKr\n"); goto leave; } memcpy (pkr, msg+16, 32); /* Put the expire value into a buffer. */ expire = string_to_u64 (xnvc_get_string (state, "Expires:")); if (!expire) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no 'Expire' in our state file\n"); goto leave; } expirebuf[0] = expire >> 32; expirebuf[1] = expire >> 24; expirebuf[2] = expire >> 16; expirebuf[3] = expire >> 8; expirebuf[4] = expire; /* Get our secret from the state. */ if (hex2bin (xnvc_get_string (state, "DH-SKi:"), ski, sizeof ski) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'DH-SKi' in our state file\n"); goto leave; } /* Compute the shared secrets. */ err = compute_master_secret (master, sizeof master, ski, sizeof ski, pkr, sizeof pkr); if (err) { log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); goto leave; } kdf (hmacikey, sizeof hmacikey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-HMACi-key"); kdf (symxkey, sizeof symxkey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-SYMx-key"); /* Create the response. */ newmsglen = 7+1+8+32+32; newmsg = xmalloc (newmsglen); memcpy (newmsg+0, "GPG-pa1", 7); newmsg[7] = MSG_TYPE_DHPART2; memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ memcpy (newmsg+16, pki, 32); /* PKi */ /* MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key) */ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); hmac_data (newmsg+48, 32, hmacikey, sizeof hmacikey, tmphash, sizeof tmphash, newmsg, (size_t)48, symxkey, sizeof symxkey, NULL); /* Update the state. */ xnvc_set (state, "State:", "DHPart2-sent"); xnvc_set_hex (state, "DH-Master:", master, sizeof master); gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, newmsg, newmsglen); xnvc_set_hex (state, "Hash-DHPart2:", tmphash, 32); /* Write the state. */ write_state (state, 0); /* Write the message. */ send_message (newmsg, newmsglen); leave: xfree (newmsg); return err; } /* We are the Responder: Process a DHPART2 message in (MSG,MSGLEN) * which has already been validated to have a correct header and * message type. Sends the CONFIRM message and writes STATE. */ static gpg_error_t proc_msg_dhpart2 (nvc_t state, const unsigned char *msg, size_t msglen) { gpg_error_t err; unsigned char hash[32]; unsigned char tmphash[32]; uint64_t expire; unsigned char expirebuf[5]; unsigned char pki[32]; unsigned char pkr[32]; unsigned char skr[32]; unsigned char master[32]; unsigned char hmacikey[32]; unsigned char hmacrkey[32]; unsigned char symxkey[32]; unsigned char sas[32]; unsigned char *newmsg = NULL; size_t newmsglen; log_assert (msglen >= 80); /* Check that the PKi in the message matches the Hash(Pki) received * with the Commit message. */ memcpy (pki, msg + 16, 32); gcry_md_hash_buffer (GCRY_MD_SHA256, hash, pki, 32); if (hex2bin (xnvc_get_string (state, "Hash-PKi:"), tmphash, sizeof tmphash) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'Hash-PKi' in our state file\n"); goto leave; } if (memcmp (hash, tmphash, 32)) { err = gpg_error (GPG_ERR_BAD_DATA); log_error ("Initiator sent a different key in %s than announced in %s\n", msgtypestr (MSG_TYPE_DHPART2), msgtypestr (MSG_TYPE_COMMIT)); goto leave; } /* Check that the received PKi is different from our PKr. */ if (hex2bin (xnvc_get_string (state, "DH-PKr:"), pkr, sizeof pkr) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'DH-PKr' in our state file\n"); goto leave; } if (!memcmp (pkr, pki, 32)) { err = gpg_error (GPG_ERR_BAD_DATA); log_error ("Initiator sent our own PKr back\n"); goto leave; } /* Put the expire value into a buffer. */ expire = string_to_u64 (xnvc_get_string (state, "Expires:")); if (!expire) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no 'Expire' in our state file\n"); goto leave; } expirebuf[0] = expire >> 32; expirebuf[1] = expire >> 24; expirebuf[2] = expire >> 16; expirebuf[3] = expire >> 8; expirebuf[4] = expire; /* Get our secret from the state. */ if (hex2bin (xnvc_get_string (state, "DH-SKr:"), skr, sizeof skr) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'DH-SKr' in our state file\n"); goto leave; } /* Compute the shared secrets. */ err = compute_master_secret (master, sizeof master, skr, sizeof skr, pki, sizeof pki); if (err) { log_error ("creating DH keypair failed: %s\n", gpg_strerror (err)); goto leave; } kdf (hmacikey, sizeof hmacikey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-HMACi-key"); kdf (hmacrkey, sizeof hmacrkey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-HMACr-key"); kdf (symxkey, sizeof symxkey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-SYMx-key"); kdf (sas, sizeof sas, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-SAS"); /* Check the MAC from the message which is * MAC(HMACi-key, Hash(DHPART1) || DHPART2[0..47] || SYMx-key). * For that we need to fetch the stored hash from the state. */ if (hex2bin (xnvc_get_string (state, "Hash-DHPart1:"), tmphash, sizeof tmphash) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'Hash-DHPart1' in our state file\n"); goto leave; } hmac_data (hash, 32, hmacikey, sizeof hmacikey, tmphash, sizeof tmphash, msg, 48, symxkey, sizeof symxkey, NULL); if (memcmp (msg+48, hash, 32)) { err = gpg_error (GPG_ERR_BAD_DATA); log_error ("manipulation of received %s message detected: %s\n", msgtypestr (MSG_TYPE_DHPART2), "Bad MAC"); goto leave; } /* Create the response. */ newmsglen = 7+1+8+32; newmsg = xmalloc (newmsglen); memcpy (newmsg+0, "GPG-pa1", 7); newmsg[7] = MSG_TYPE_CONFIRM; memcpy (newmsg+8, msg + 8, 8); /* SessionID. */ /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key) */ gcry_md_hash_buffer (GCRY_MD_SHA256, tmphash, msg, msglen); hmac_data (newmsg+16, 32, hmacrkey, sizeof hmacrkey, tmphash, sizeof tmphash, newmsg, (size_t)16, symxkey, sizeof symxkey, NULL); /* Update the state. */ xnvc_set (state, "State:", "Confirm-sent"); xnvc_set_hex (state, "DH-Master:", master, sizeof master); /* Write the state. */ write_state (state, 0); /* Write the message. */ send_message (newmsg, newmsglen); display_sas (sas, sizeof sas, 0); leave: xfree (newmsg); return err; } /* We are the Initiator: Process a CONFIRM message in (MSG,MSGLEN) * which has already been validated to have a correct header and * message type. Does not send anything back. */ static gpg_error_t proc_msg_confirm (nvc_t state, const unsigned char *msg, size_t msglen) { gpg_error_t err; unsigned char hash[32]; unsigned char tmphash[32]; unsigned char master[32]; uint64_t expire; unsigned char expirebuf[5]; unsigned char hmacrkey[32]; unsigned char symxkey[32]; unsigned char sas[32]; log_assert (msglen >= 48); /* Put the expire value into a buffer. */ expire = string_to_u64 (xnvc_get_string (state, "Expires:")); if (!expire) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no 'Expire' in our state file\n"); goto leave; } expirebuf[0] = expire >> 32; expirebuf[1] = expire >> 24; expirebuf[2] = expire >> 16; expirebuf[3] = expire >> 8; expirebuf[4] = expire; /* Get the master secret. */ if (hex2bin (xnvc_get_string (state, "DH-Master:"),master,sizeof master) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'DH-Master' in our state file\n"); goto leave; } kdf (hmacrkey, sizeof hmacrkey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-HMACr-key"); kdf (symxkey, sizeof symxkey, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-SYMx-key"); kdf (sas, sizeof sas, master, sizeof master, msg+8, 8, expirebuf, sizeof expirebuf, "GPG-pa1-SAS"); /* Check the MAC from the message which is */ /* MAC(HMACr-key, Hash(DHPART2) || CONFIRM[0..15] || SYMx-key). */ if (hex2bin (xnvc_get_string (state, "Hash-DHPart2:"), tmphash, sizeof tmphash) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("no or garbled 'Hash-DHPart2' in our state file\n"); goto leave; } hmac_data (hash, 32, hmacrkey, sizeof hmacrkey, tmphash, sizeof tmphash, msg, (size_t)16, symxkey, sizeof symxkey, NULL); if (!memcmp (msg+48, hash, 32)) { err = gpg_error (GPG_ERR_BAD_DATA); log_error ("manipulation of received %s message detected: %s\n", msgtypestr (MSG_TYPE_CONFIRM), "Bad MAC"); goto leave; } err = display_sas (sas, sizeof sas, 1); if (err) goto leave; /* Update the state. */ xnvc_set (state, "State:", "Confirmed"); /* Write the state. */ write_state (state, 0); leave: return err; } /* Expire old state files. This loops over all state files and remove * those which are expired. */ static void expire_old_states (void) { gpg_error_t err = 0; const char *dirname; gnupg_dir_t dir = NULL; gnupg_dirent_t dir_entry; char *fname = NULL; estream_t fp = NULL; nvc_t nvc = NULL; nve_t item; const char *value; unsigned long expire; unsigned long now = gnupg_get_time (); dirname = get_pairing_statedir (); dir = gnupg_opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); goto leave; } while ((dir_entry = gnupg_readdir (dir))) { if (strlen (dir_entry->d_name) != 16+4 || strcmp (dir_entry->d_name + 16, ".pa1")) continue; xfree (fname); fname = make_filename (dirname, dir_entry->d_name, NULL); es_fclose (fp); fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_info ("failed to open state file '%s': %s\n", fname, gpg_strerror (err)); continue; } nvc_release (nvc); /* NB.: The following is similar to code in read_state. */ err = nvc_parse (&nvc, NULL, fp); if (err) { log_info ("failed to parse state file '%s': %s\n", fname, gpg_strerror (err)); continue; /* Skip */ } item = nvc_lookup (nvc, "Expires:"); if (!item) { log_info ("invalid state file '%s': %s\n", fname, "field 'expire' not found"); continue; /* Skip */ } value = nve_value (item); if (!value || !(expire = strtoul (value, NULL, 10))) { log_info ("invalid state file '%s': %s\n", fname, "field 'expire' has an invalid value"); continue; /* Skip */ } if (expire <= now) { es_fclose (fp); fp = NULL; if (gnupg_remove (fname)) { err = gpg_error_from_syserror (); log_info ("failed to delete state file '%s': %s\n", fname, gpg_strerror (err)); } else if (opt.verbose) log_info ("state file '%s' deleted\n", fname); } } leave: if (err) log_error ("expiring old states in '%s' failed: %s\n", dirname, gpg_strerror (err)); gnupg_closedir (dir); es_fclose (fp); xfree (fname); } /* Initiate a pairing. The output needs to be conveyed to the * peer */ static gpg_error_t command_initiate (void) { gpg_error_t err; nvc_t state; state = xnvc_new (); xnvc_set (state, "Version:", "GPG-pa1"); xnvc_set_hex (state, "Session:", get_session_id (), 8); xnvc_set (state, "Role:", "Initiator"); err = make_msg_commit (state); nvc_release (state); return err; } /* Helper for command_respond(). */ static gpg_error_t expect_state (int msgtype, const char *statestr, const char *expected) { if (strcmp (statestr, expected)) { log_error ("received %s message in %s state (should be %s)\n", msgtypestr (msgtype), statestr, expected); return gpg_error (GPG_ERR_INV_RESPONSE); } return 0; } /* Respond to a pairing initiation. This is used by the peer and later * by the original responder. Depending on the state the output needs * to be conveyed to the peer. */ static gpg_error_t command_respond (void) { gpg_error_t err; unsigned char *msg; size_t msglen = 0; /* In case that read_message returns an error. */ int msgtype = 0; /* ditto. */ nvc_t state; const char *rolestr; const char *statestr; err = read_message (&msg, &msglen, &msgtype, &state); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; rolestr = xnvc_get_string (state, "Role:"); statestr = xnvc_get_string (state, "State:"); if (DBG_MESSAGE) { if (!state) log_debug ("no state available\n"); else log_debug ("we are %s, our current state is %s\n", rolestr, statestr); log_debug ("got message of type %s (%d)\n", msgtypestr (msgtype), msgtype); } if (!state) { if (msgtype == MSG_TYPE_COMMIT) { state = xnvc_new (); xnvc_set (state, "Version:", "GPG-pa1"); xnvc_set_hex (state, "Session:", get_session_id (), 8); xnvc_set (state, "Role:", "Responder"); err = proc_msg_commit (state, msg, msglen); } else { log_error ("%s message expected but got %s\n", msgtypestr (MSG_TYPE_COMMIT), msgtypestr (msgtype)); if (msgtype == MSG_TYPE_DHPART1) log_info ("the pairing probably took too long and timed out\n"); err = gpg_error (GPG_ERR_INV_RESPONSE); goto leave; } } else if (!strcmp (rolestr, "Initiator")) { if (msgtype == MSG_TYPE_DHPART1) { if (!(err = expect_state (msgtype, statestr, "Commit-sent"))) err = proc_msg_dhpart1 (state, msg, msglen); } else if (msgtype == MSG_TYPE_CONFIRM) { if (!(err = expect_state (msgtype, statestr, "DHPart2-sent"))) err = proc_msg_confirm (state, msg, msglen); } else { log_error ("%s message not expected by Initiator\n", msgtypestr (msgtype)); err = gpg_error (GPG_ERR_INV_RESPONSE); goto leave; } } else if (!strcmp (rolestr, "Responder")) { if (msgtype == MSG_TYPE_DHPART2) { if (!(err = expect_state (msgtype, statestr, "DHPart1-sent"))) err = proc_msg_dhpart2 (state, msg, msglen); } else { log_error ("%s message not expected by Responder\n", msgtypestr (msgtype)); err = gpg_error (GPG_ERR_INV_RESPONSE); goto leave; } } else log_fatal ("invalid role '%s' in state file\n", rolestr); leave: xfree (msg); nvc_release (state); return err; } /* Return the keys for SESSIONIDSTR or the last one if it is NULL. * Two keys are returned: The first is the one for sending encrypted * data and the second one for decrypting received data. The keys are * always returned hex encoded and both are terminated by a LF. */ static gpg_error_t command_get (const char *sessionidstr) { gpg_error_t err; unsigned char sessid[8]; nvc_t state; if (!sessionidstr) { log_error ("calling without session-id is not yet implemented\n"); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); goto leave; } if (hex2bin (sessionidstr, sessid, sizeof sessid) < 0) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("invalid session id given\n"); goto leave; } set_session_id (sessid, sizeof sessid); err = read_state (&state); if (err) { log_error ("reading state of session %s failed: %s\n", sessionidstr, gpg_strerror (err)); goto leave; } leave: return err; } /* Cleanup command. */ static gpg_error_t command_cleanup (void) { expire_old_states (); return 0; } diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 521222631..ee0554014 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -1,2082 +1,2082 @@ /* gpg-wks-client.c - A client for the Web Key Service protocols. * Copyright (C) 2016, 2022 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/mbox-util.h" #include "../common/name-value.h" #include "../common/comopt.h" #include "call-dirmngr.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oOutput = 'o', oDirectory = 'C', oDebug = 500, aSupported, aCheck, aCreate, aReceive, aRead, aMirror, aInstallKey, aRemoveKey, aPrintWKDHash, aPrintWKDURL, oGpgProgram, oSend, oFakeSubmissionAddr, oStatusFD, oWithColons, oBlacklist, oNoAutostart, oAddRevocs, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, ("@Commands:\n ")), ARGPARSE_c (aSupported, "supported", ("check whether provider supports WKS")), ARGPARSE_c (aCheck, "check", ("check whether a key is available")), ARGPARSE_c (aCreate, "create", ("create a publication request")), ARGPARSE_c (aReceive, "receive", ("receive a MIME confirmation request")), ARGPARSE_c (aRead, "read", ("receive a plain text confirmation request")), ARGPARSE_c (aMirror, "mirror", "mirror an LDAP directory"), ARGPARSE_c (aInstallKey, "install-key", "install a key into a directory"), ARGPARSE_c (aRemoveKey, "remove-key", "remove a key from a directory"), ARGPARSE_c (aPrintWKDHash, "print-wkd-hash", "print the WKD identifier for the given user ids"), ARGPARSE_c (aPrintWKDURL, "print-wkd-url", "print the WKD URL for the given user id"), ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"), ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"), - ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_s (oBlacklist, "blacklist", "@"), ARGPARSE_s_s (oDirectory, "directory", "@"), ARGPARSE_s_n (oAddRevocs, "add-revocs", "add revocation certificates"), ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MIME_VALUE , "mime" }, { DBG_PARSER_VALUE , "parser" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* Value of the option --fake-submission-addr. */ const char *fake_submission_addr; /* An array with blacklisted addresses and its length. Use * is_in_blacklist to check. */ static char **blacklist_array; static size_t blacklist_array_len; static void wrong_args (const char *text) GPGRT_ATTR_NORETURN; static void add_blacklist (const char *fname); static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text); static gpg_error_t command_supported (char *userid); static gpg_error_t command_check (char *userid); static gpg_error_t command_send (const char *fingerprint, const char *userid); static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint); static gpg_error_t read_confirmation_request (estream_t msg); static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t fp, unsigned int flags); static gpg_error_t command_mirror (char *domain[]); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "LGPL-2.1-or-later"; break; case 11: p = "gpg-wks-client"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-wks-client [command] [options] [args] (-h for help)"); break; case 41: p = ("Syntax: gpg-wks-client [command] [options] [args]\n" "Client for the Web Key Service\n"); break; default: p = NULL; break; } return p; } static void wrong_args (const char *text) { es_fprintf (es_stderr, _("usage: %s [options] %s\n"), gpgrt_strusage (11), text); exit (2); } /* Command line parsing. */ static enum cmd_and_opt_values parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { enum cmd_and_opt_values cmd = 0; int no_more_options = 0; while (!no_more_options && gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oSend: opt.use_sendmail = 1; break; case oOutput: opt.output = pargs->r.ret_str; break; case oFakeSubmissionAddr: fake_submission_addr = pargs->r.ret_str; break; case oStatusFD: - wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); + wks_set_status_fd (translate_sys2libc_fdstr (pargs->r.ret_str, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.no_autostart = 1; break; case oBlacklist: add_blacklist (pargs->r.ret_str); break; case oAddRevocs: opt.add_revocs = 1; break; case aSupported: case aCreate: case aReceive: case aRead: case aCheck: case aMirror: case aInstallKey: case aRemoveKey: case aPrintWKDHash: case aPrintWKDURL: cmd = pargs->r_opt; break; default: pargs->err = ARGPARSE_PRINT_ERROR; break; } } return cmd; } /* gpg-wks-client main. */ int main (int argc, char **argv) { gpg_error_t err, delayed_err; gpgrt_argparse_t pargs; enum cmd_and_opt_values cmd; gnupg_reopen_std ("gpg-wks-client"); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-wks-client", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; cmd = parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Check if gpg is build with sendmail support */ if (opt.use_sendmail && !NAME_OF_SENDMAIL[0]) { err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); log_error ("sending mail is not supported in this build: %s\n", gpg_strerror (err)); } if (log_get_errorcount (0)) exit (2); /* Process common component options. Note that we set the config * dir only here so that --homedir will have an effect. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); if (parse_comopt (GNUPG_MODULE_NAME_CONNECT_AGENT, opt.verbose > 1)) exit(2); if (comopt.no_autostart) opt.no_autostart = 1; /* 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]); } /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.directory) opt.directory = "openpgpkey"; /* Tell call-dirmngr what options we want. */ set_dirmngr_options (opt.verbose, (opt.debug & DBG_IPC_VALUE), !opt.no_autostart); /* Check that the top directory exists. */ if (cmd == aInstallKey || cmd == aRemoveKey || cmd == aMirror) { struct stat sb; if (gnupg_stat (opt.directory, &sb)) { err = gpg_error_from_syserror (); log_error ("error accessing directory '%s': %s\n", opt.directory, gpg_strerror (err)); goto leave; } if (!S_ISDIR(sb.st_mode)) { log_error ("error accessing directory '%s': %s\n", opt.directory, "not a directory"); err = gpg_error (GPG_ERR_ENOENT); goto leave; } } /* Run the selected command. */ switch (cmd) { case aSupported: if (opt.with_colons) { for (; argc; argc--, argv++) command_supported (*argv); err = 0; } else { if (argc != 1) wrong_args ("--supported DOMAIN"); err = command_supported (argv[0]); if (err && gpg_err_code (err) != GPG_ERR_FALSE) log_error ("checking support failed: %s\n", gpg_strerror (err)); } break; case aCreate: if (argc != 2) wrong_args ("--create FINGERPRINT USER-ID"); err = command_send (argv[0], argv[1]); if (err) log_error ("creating request failed: %s\n", gpg_strerror (err)); break; case aReceive: if (argc) wrong_args ("--receive < MIME-DATA"); err = wks_receive (es_stdin, command_receive_cb, NULL); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aRead: if (argc) wrong_args ("--read < WKS-DATA"); err = read_confirmation_request (es_stdin); if (err) log_error ("processing mail failed: %s\n", gpg_strerror (err)); break; case aCheck: if (argc != 1) wrong_args ("--check USER-ID"); err = command_check (argv[0]); break; case aMirror: if (!argc) err = command_mirror (NULL); else err = command_mirror (argv); break; case aInstallKey: if (!argc) err = wks_cmd_install_key (NULL, NULL); else if (argc == 2) err = wks_cmd_install_key (*argv, argv[1]); else wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]"); break; case aRemoveKey: if (argc != 1) wrong_args ("--remove-key USER-ID"); err = wks_cmd_remove_key (*argv); break; case aPrintWKDHash: case aPrintWKDURL: if (!argc) { if (cmd == aPrintWKDHash) err = proc_userid_from_stdin (wks_cmd_print_wkd_hash, "printing WKD hash"); else err = proc_userid_from_stdin (wks_cmd_print_wkd_url, "printing WKD URL"); } else { for (err = delayed_err = 0; !err && argc; argc--, argv++) { if (cmd == aPrintWKDHash) err = wks_cmd_print_wkd_hash (*argv); else err = wks_cmd_print_wkd_url (*argv); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { /* Diagnostic already printed. */ delayed_err = err; err = 0; } else if (err) log_error ("printing hash failed: %s\n", gpg_strerror (err)); } if (!err) err = delayed_err; } break; default: gpgrt_usage (1); err = 0; break; } leave: if (err) wks_write_status (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) wks_write_status (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else wks_write_status (STATUS_SUCCESS, NULL); return (err || log_get_errorcount (0))? 1:0; } /* Read a file FNAME into a buffer and return that malloced buffer. * Caller must free the buffer. On error NULL is returned, on success * the valid length of the buffer is stored at R_LENGTH. The returned * buffer is guaranteed to be Nul terminated. */ static char * read_file (const char *fname, size_t *r_length) { estream_t fp; char *buf; size_t buflen; if (!strcmp (fname, "-")) { size_t nread, bufsize = 0; fp = es_stdin; es_set_binary (fp); buf = NULL; buflen = 0; #define NCHUNK 32767 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize+1); else buf = xrealloc (buf, bufsize+1); nread = es_fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && es_ferror (fp)) { log_error ("error reading '[stdin]': %s\n", strerror (errno)); xfree (buf); return NULL; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK } else { struct stat st; fp = es_fopen (fname, "rb"); if (!fp) { log_error ("can't open '%s': %s\n", fname, strerror (errno)); return NULL; } if (fstat (es_fileno (fp), &st)) { log_error ("can't stat '%s': %s\n", fname, strerror (errno)); es_fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); if (es_fread (buf, buflen, 1, fp) != 1) { log_error ("error reading '%s': %s\n", fname, strerror (errno)); es_fclose (fp); xfree (buf); return NULL; } es_fclose (fp); } buf[buflen] = 0; if (r_length) *r_length = buflen; return buf; } static int cmp_blacklist (const void *arg_a, const void *arg_b) { const char *a = *(const char **)arg_a; const char *b = *(const char **)arg_b; return strcmp (a, b); } /* Add a blacklist to our global table. This is called during option * parsing and thus any use of log_error will eventually stop further * processing. */ static void add_blacklist (const char *fname) { char *buffer; char *p, *pend; char **array; size_t arraysize, arrayidx; buffer = read_file (fname, NULL); if (!buffer) return; /* Estimate the number of entries by counting the non-comment lines. */ arraysize = 2; /* For the first and an extra NULL item. */ for (p=buffer; *p; p++) if (*p == '\n' && p[1] && p[1] != '#') arraysize++; array = xcalloc (arraysize, sizeof *array); arrayidx = 0; /* Loop over all lines. */ for (p = buffer; p && *p; p = pend) { pend = strchr (p, '\n'); if (pend) *pend++ = 0; trim_spaces (p); if (!*p || *p == '#' ) continue; ascii_strlwr (p); log_assert (arrayidx < arraysize); array[arrayidx] = p; arrayidx++; } log_assert (arrayidx < arraysize); qsort (array, arrayidx, sizeof *array, cmp_blacklist); blacklist_array = array; blacklist_array_len = arrayidx; gpgrt_annotate_leaked_object (buffer); gpgrt_annotate_leaked_object (blacklist_array); } /* Return true if NAME is in a blacklist. */ static int is_in_blacklist (const char *name) { if (!name || !blacklist_array) return 0; return !!bsearch (&name, blacklist_array, blacklist_array_len, sizeof *blacklist_array, cmp_blacklist); } /* Read user ids from stdin and call FUNC for each user id. TEXT is * used for error messages. */ static gpg_error_t proc_userid_from_stdin (gpg_error_t (*func)(const char *), const char *text) { gpg_error_t err = 0; gpg_error_t delayed_err = 0; char line[2048]; size_t n = 0; /* If we are on a terminal disable buffering to get direct response. */ if (gnupg_isatty (es_fileno (es_stdin)) && gnupg_isatty (es_fileno (es_stdout))) { es_setvbuf (es_stdin, NULL, _IONBF, 0); es_setvbuf (es_stdout, NULL, _IOLBF, 0); } while (es_fgets (line, sizeof line - 1, es_stdin)) { n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); log_error ("error reading stdin: %s\n", gpg_strerror (err)); break; } trim_spaces (line); err = func (line); if (gpg_err_code (err) == GPG_ERR_INV_USER_ID) { delayed_err = err; err = 0; } else if (err) log_error ("%s failed: %s\n", text, gpg_strerror (err)); } if (es_ferror (es_stdin)) { err = gpg_error_from_syserror (); log_error ("error reading stdin: %s\n", gpg_strerror (err)); goto leave; } leave: if (!err) err = delayed_err; return err; } /* Add the user id UID to the key identified by FINGERPRINT. */ static gpg_error_t add_user_id (const char *fingerprint, const char *uid) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--quick-add-uid"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, uid); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, NULL, NULL, NULL); if (err) { log_error ("adding user id failed: %s\n", gpg_strerror (err)); goto leave; } leave: xfree (argv); return err; } struct decrypt_stream_parm_s { char *fpr; char *mainfpr; int otrust; }; static void decrypt_stream_status_cb (void *opaque, const char *keyword, char *args) { struct decrypt_stream_parm_s *decinfo = opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "DECRYPTION_KEY") && !decinfo->fpr) { const char *fields[3]; if (split_fields (args, fields, DIM (fields)) >= 3) { decinfo->fpr = xstrdup (fields[0]); decinfo->mainfpr = xstrdup (fields[1]); decinfo->otrust = *fields[2]; } } } /* Decrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. */ static gpg_error_t decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo, estream_t input) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; *r_output = NULL; memset (decinfo, 0, sizeof *decinfo); output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); /* We limit the output to 64 KiB to avoid DoS using compression * tricks. A regular client will anyway only send a minimal key; * that is one w/o key signatures and attribute packets. */ ccparray_put (&ccp, "--max-output=0x10000"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--decrypt"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, decrypt_stream_status_cb, decinfo); if (!err && (!decinfo->fpr || !decinfo->mainfpr || !decinfo->otrust)) err = gpg_error (GPG_ERR_INV_ENGINE); if (err) { log_error ("decryption failed: %s\n", gpg_strerror (err)); goto leave; } else if (opt.verbose) log_info ("decryption succeeded\n"); es_rewind (output); *r_output = output; output = NULL; leave: if (err) { xfree (decinfo->fpr); xfree (decinfo->mainfpr); memset (decinfo, 0, sizeof *decinfo); } es_fclose (output); xfree (argv); return err; } /* Return the submission address for the address or just the domain in * ADDRSPEC. The submission address is stored as a malloced string at * R_SUBMISSION_ADDRESS. At R_POLICY the policy flags of the domain * are stored. The caller needs to free them with wks_free_policy. * The function returns an error code on failure to find a submission * address or policy file. Note: The function may store NULL at * R_SUBMISSION_ADDRESS but return success to indicate that the web * key directory is supported but not the web key service. As per WKD * specs a policy file is always required and will thus be return on * success. */ static gpg_error_t get_policy_and_sa (const char *addrspec, int silent, policy_flags_t *r_policy, char **r_submission_address) { gpg_error_t err; estream_t mbuf = NULL; const char *domain; const char *s; policy_flags_t policy = NULL; char *submission_to = NULL; *r_submission_address = NULL; *r_policy = NULL; domain = strchr (addrspec, '@'); if (domain) domain++; if (opt.with_colons) { s = domain? domain : addrspec; es_write_sanitized (es_stdout, s, strlen (s), ":", NULL); es_putc (':', es_stdout); } /* We first try to get the submission address from the policy file * (this is the new method). If both are available we check that * they match and print a warning if not. In the latter case we * keep on using the one from the submission-address file. */ err = wkd_get_policy_flags (addrspec, &mbuf); if (err && gpg_err_code (err) != GPG_ERR_NO_DATA && gpg_err_code (err) != GPG_ERR_NO_NAME) { if (!opt.with_colons) log_error ("error reading policy flags for '%s': %s\n", domain, gpg_strerror (err)); goto leave; } if (!mbuf) { if (!opt.with_colons) log_error ("provider for '%s' does NOT support the Web Key Directory\n", addrspec); err = gpg_error (GPG_ERR_FALSE); goto leave; } policy = xtrycalloc (1, sizeof *policy); if (!policy) err = gpg_error_from_syserror (); else err = wks_parse_policy (policy, mbuf, 1); es_fclose (mbuf); mbuf = NULL; if (err) goto leave; err = wkd_get_submission_address (addrspec, &submission_to); if (err && !policy->submission_address) { if (!silent && !opt.with_colons) log_error (_("error looking up submission address for domain '%s'" ": %s\n"), domain, gpg_strerror (err)); if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons) log_error (_("this domain probably doesn't support WKS.\n")); goto leave; } if (submission_to && policy->submission_address && ascii_strcasecmp (submission_to, policy->submission_address)) log_info ("Warning: different submission addresses (sa=%s, po=%s)\n", submission_to, policy->submission_address); if (!submission_to && policy->submission_address) { submission_to = xtrystrdup (policy->submission_address); if (!submission_to) { err = gpg_error_from_syserror (); goto leave; } } leave: *r_submission_address = submission_to; submission_to = NULL; *r_policy = policy; policy = NULL; if (opt.with_colons) { if (*r_policy && !*r_submission_address) es_fprintf (es_stdout, "1:0::"); else if (*r_policy && *r_submission_address) es_fprintf (es_stdout, "1:1::"); else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)) es_fprintf (es_stdout, "0:0:%d:", err); else es_fprintf (es_stdout, "0:0::"); if (*r_policy) { es_fprintf (es_stdout, "%u:%u:%u:", (*r_policy)->protocol_version, (*r_policy)->auth_submit, (*r_policy)->mailbox_only); } es_putc ('\n', es_stdout); } xfree (submission_to); wks_free_policy (policy); xfree (policy); es_fclose (mbuf); return err; } /* Check whether the provider supports the WKS protocol. */ static gpg_error_t command_supported (char *userid) { gpg_error_t err; char *addrspec = NULL; char *submission_to = NULL; policy_flags_t policy = NULL; if (!strchr (userid, '@')) { char *tmp = xstrconcat ("foo@", userid, NULL); addrspec = mailbox_from_userid (tmp, 0); xfree (tmp); } else addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = get_policy_and_sa (addrspec, 1, &policy, &submission_to); if (err || !submission_to) { if (!submission_to || gpg_err_code (err) == GPG_ERR_FALSE || gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST ) { /* FALSE is returned if we already figured out that even the * Web Key Directory is not supported and thus printed an * error message. */ if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE && !opt.with_colons) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) log_info ("provider for '%s' does NOT support WKS\n", addrspec); else log_info ("provider for '%s' does NOT support WKS (%s)\n", addrspec, gpg_strerror (err)); } err = gpg_error (GPG_ERR_FALSE); if (!opt.with_colons) log_inc_errorcount (); } goto leave; } if (opt.verbose && !opt.with_colons) log_info ("provider for '%s' supports WKS\n", addrspec); leave: wks_free_policy (policy); xfree (policy); xfree (submission_to); xfree (addrspec); return err; } /* Check whether the key for USERID is available in the WKD. */ static gpg_error_t command_check (char *userid) { gpg_error_t err; char *addrspec = NULL; estream_t key = NULL; char *fpr = NULL; uidinfo_list_t mboxes = NULL; uidinfo_list_t sl; int found = 0; addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } /* Get the submission address. */ err = wkd_get_key (addrspec, &key); switch (gpg_err_code (err)) { case 0: if (opt.verbose) log_info ("public key for '%s' found via WKD\n", addrspec); /* Fixme: Check that the key contains the user id. */ break; case GPG_ERR_NO_DATA: /* No such key. */ if (opt.verbose) log_info ("public key for '%s' NOT found via WKD\n", addrspec); err = gpg_error (GPG_ERR_NO_PUBKEY); log_inc_errorcount (); break; case GPG_ERR_UNKNOWN_HOST: if (opt.verbose) log_info ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; default: log_error ("error looking up '%s' via WKD: %s\n", addrspec, gpg_strerror (err)); break; } if (err) goto leave; /* Look closer at the key. */ err = wks_list_key (key, &fpr, &mboxes); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (opt.verbose) log_info ("fingerprint: %s\n", fpr); for (sl = mboxes; sl; sl = sl->next) { if (sl->mbox && !strcmp (sl->mbox, addrspec)) found = 1; if (opt.verbose) { log_info (" user-id: %s\n", sl->uid); log_info (" created: %s\n", asctimestamp (sl->created)); if (sl->mbox) log_info (" addr-spec: %s\n", sl->mbox); if (sl->expired || sl->revoked) log_info (" flags:%s%s\n", sl->expired? " expired":"", sl->revoked?" revoked":""); } } if (!found) { log_error ("public key for '%s' has no user id with the mail address\n", addrspec); err = gpg_error (GPG_ERR_CERT_REVOKED); } else if (opt.output) { /* Save to file. */ const char *fname = opt.output; if (*fname == '-' && !fname[1]) fname = NULL; es_rewind (key); err = wks_write_to_file (key, fname); if (err) log_error ("writing key to '%s' failed: %s\n", fname? fname : "[stdout]", gpg_strerror (err)); } leave: xfree (fpr); free_uidinfo_list (mboxes); es_fclose (key); xfree (addrspec); return err; } /* Locate the key by fingerprint and userid and send a publication * request. */ static gpg_error_t command_send (const char *fingerprint, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; char *addrspec = NULL; estream_t key = NULL; estream_t keyenc = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; policy_flags_t policy = NULL; int no_encrypt = 0; int posteo_hack = 0; const char *domain; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; int any; if (classify_user_id (fingerprint, &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fingerprint); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error (_("\"%s\" is not a proper mail address\n"), userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } err = wks_get_key (&key, fingerprint, addrspec, 0, 1); if (err) goto leave; domain = strchr (addrspec, '@'); log_assert (domain); domain++; /* Get the submission address. */ if (fake_submission_addr) { policy = xcalloc (1, sizeof *policy); submission_to = xstrdup (fake_submission_addr); err = 0; } else { err = get_policy_and_sa (addrspec, 0, &policy, &submission_to); if (err) goto leave; if (!submission_to) { log_error (_("this domain probably doesn't support WKS.\n")); err = gpg_error (GPG_ERR_NO_DATA); goto leave; } } log_info ("submitting request to '%s'\n", submission_to); if (policy->auth_submit) log_info ("no confirmation required for '%s'\n", addrspec); /* In case the key has several uids with the same addr-spec we will * use the newest one. */ err = wks_list_key (key, NULL, &uidlist); if (err) { log_error ("error parsing key: %s\n",gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; any = 0; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox)) continue; /* UID has more than just the mailbox. */ if (uid->expired) { if (opt.verbose) log_info ("ignoring expired user id '%s'\n", uid->uid); continue; } any = 1; if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (!any) { log_error ("public key %s has no mail address '%s'\n", fingerprint, addrspec); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (opt.verbose) log_info ("submitting key with user id '%s'\n", thisuid->uid); /* If we have more than one user id we need to filter the key to * include only THISUID. */ if (uidlist->next) { estream_t newkey; es_rewind (key); err = wks_filter_uid (&newkey, key, thisuid->uid, 1); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (key); key = newkey; } if (policy->mailbox_only && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox))) { log_info ("Warning: policy requires 'mailbox-only'" " - adding user id '%s'\n", addrspec); err = add_user_id (fingerprint, addrspec); if (err) goto leave; /* Need to get the key again. This time we request filtering * for the full user id, so that we do not need check and filter * the key again. */ es_fclose (key); key = NULL; err = wks_get_key (&key, fingerprint, addrspec, 1, 1); if (err) goto leave; } if (opt.add_revocs) { if (es_fseek (key, 0, SEEK_END)) { err = gpg_error_from_syserror (); log_error ("error seeking stream: %s\n", gpg_strerror (err)); goto leave; } err = wks_find_add_revocs (key, addrspec); if (err) { log_error ("error finding revocations for '%s': %s\n", addrspec, gpg_strerror (err)); goto leave; } } /* Now put the armor around the key. */ { estream_t newkey; es_rewind (key); err = wks_armor_key (&newkey, key, no_encrypt? NULL /* */ : ("Content-Type: application/pgp-keys\n" "\n")); if (err) { log_error ("error armoring key: %s\n", gpg_strerror (err)); goto leave; } es_fclose (key); key = newkey; } /* Hack to support posteo but let them disable this by setting the * new policy-version flag. */ if (policy->protocol_version < 3 && !ascii_strcasecmp (domain, "posteo.de")) { log_info ("Warning: Using draft-1 method for domain '%s'\n", domain); no_encrypt = 1; posteo_hack = 1; } /* Encrypt the key part. */ if (!no_encrypt) { es_rewind (key); err = encrypt_response (&keyenc, key, submission_to, fingerprint); if (err) goto leave; es_fclose (key); key = NULL; } /* Send the key. */ err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", addrspec); if (err) goto leave; err = mime_maker_add_header (mime, "To", submission_to); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publishing request"); if (err) goto leave; /* Tell server which draft we support. */ err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (no_encrypt) { void *data; size_t datalen; if (posteo_hack) { /* Needs a multipart/mixed with one(!) attachment. It does * not grok a non-multipart mail. */ err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed"); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; } err = mime_maker_add_header (mime, "Content-type", "application/pgp-keys"); if (err) goto leave; if (es_fclose_snatch (key, &data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; err = mime_maker_add_body_data (mime, data, datalen); xfree (data); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &keyenc); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); xfree (submission_to); free_uidinfo_list (uidlist); es_fclose (keyenc); es_fclose (key); wks_free_policy (policy); xfree (policy); xfree (addrspec); return err; } static void encrypt_response_status_cb (void *opaque, const char *keyword, char *args) { gpg_error_t *failure = opaque; const char *fields[2]; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); if (!strcmp (keyword, "FAILURE")) { if (split_fields (args, fields, DIM (fields)) >= 2 && !strcmp (fields[0], "encrypt")) *failure = strtoul (fields[1], NULL, 10); } } /* Encrypt the INPUT stream to a new stream which is stored at success * at R_OUTPUT. Encryption is done for ADDRSPEC and for FINGERPRINT * (so that the sent message may later be inspected by the user). We * currently retrieve that key from the WKD, DANE, or from "local". * "local" is last to prefer the latest key version but use a local * copy in case we are working offline. It might be useful for the * server to send the fingerprint of its encryption key - or even the * entire key back. */ static gpg_error_t encrypt_response (estream_t *r_output, estream_t input, const char *addrspec, const char *fingerprint) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t output; gpg_error_t gpg_err = 0; *r_output = NULL; output = es_fopenmem (0, "w+b"); if (!output) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */ if (fake_submission_addr) ccparray_put (&ccp, "--auto-key-locate=clear,local"); else ccparray_put (&ccp, "--auto-key-locate=clear,wkd,dane,local"); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, addrspec); ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, "--encrypt"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (opt.gpg_program, argv, input, NULL, output, encrypt_response_status_cb, &gpg_err); if (err) { if (gpg_err) err = gpg_err; log_error ("encryption failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (output); *r_output = output; output = NULL; leave: es_fclose (output); xfree (argv); return err; } static gpg_error_t send_confirmation_response (const char *sender, const char *address, const char *nonce, int encrypt, const char *fingerprint) { gpg_error_t err; estream_t body = NULL; estream_t bodyenc = NULL; mime_maker_t mime = NULL; body = es_fopenmem (0, "w+b"); if (!body) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* It is fine to use 8 bit encoding because that is encrypted and * only our client will see it. */ if (encrypt) { es_fputs ("Content-Type: application/vnd.gnupg.wks\n" "Content-Transfer-Encoding: 8bit\n" "\n", body); } es_fprintf (body, ("type: confirmation-response\n" "sender: %s\n" "address: %s\n" "nonce: %s\n"), sender, address, nonce); es_rewind (body); if (encrypt) { err = encrypt_response (&bodyenc, body, sender, fingerprint); if (err) goto leave; es_fclose (body); body = NULL; } err = mime_maker_new (&mime, NULL); if (err) goto leave; err = mime_maker_add_header (mime, "From", address); if (err) goto leave; err = mime_maker_add_header (mime, "To", sender); if (err) goto leave; err = mime_maker_add_header (mime, "Subject", "Key publication confirmation"); if (err) goto leave; err = mime_maker_add_header (mime, "Wks-Draft-Version", STR2(WKS_DRAFT_VERSION)); if (err) goto leave; if (encrypt) { err = mime_maker_add_header (mime, "Content-Type", "multipart/encrypted; " "protocol=\"application/pgp-encrypted\""); if (err) goto leave; err = mime_maker_add_container (mime); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/pgp-encrypted"); if (err) goto leave; err = mime_maker_add_body (mime, "Version: 1\n"); if (err) goto leave; err = mime_maker_add_header (mime, "Content-Type", "application/octet-stream"); if (err) goto leave; err = mime_maker_add_stream (mime, &bodyenc); if (err) goto leave; } else { err = mime_maker_add_header (mime, "Content-Type", "application/vnd.gnupg.wks"); if (err) goto leave; err = mime_maker_add_stream (mime, &body); if (err) goto leave; } err = wks_send_mime (mime); leave: mime_maker_release (mime); es_fclose (bodyenc); es_fclose (body); return err; } /* Reply to a confirmation request. The MSG has already been * decrypted and we only need to send the nonce back. MAINFPR is * either NULL or the primary key fingerprint of the key used to * decrypt the request. */ static gpg_error_t process_confirmation_request (estream_t msg, const char *mainfpr) { gpg_error_t err; nvc_t nvc; nve_t item; const char *value, *sender, *address, *fingerprint, *nonce; err = nvc_parse (&nvc, NULL, msg); if (err) { log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err)); goto leave; } if (DBG_MIME) { log_debug ("request follows:\n"); nvc_write (nvc, log_get_stream ()); } /* Check that this is a confirmation request. */ if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item)) && !strcmp (value, "confirmation-request"))) { if (item && value) log_error ("received unexpected wks message '%s'\n", value); else log_error ("received invalid wks message: %s\n", "'type' missing"); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); goto leave; } /* Get the fingerprint. */ if (!((item = nvc_lookup (nvc, "fingerprint:")) && (value = nve_value (item)) && strlen (value) >= 40)) { log_error ("received invalid wks message: %s\n", "'fingerprint' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } fingerprint = value; /* Check that the fingerprint matches the key used to decrypt the * message. In --read mode or with the old format we don't have the * decryption key; thus we can't bail out. */ if (!mainfpr || ascii_strcasecmp (mainfpr, fingerprint)) { log_info ("target fingerprint: %s\n", fingerprint); log_info ("but decrypted with: %s\n", mainfpr); log_error ("confirmation request not decrypted with target key\n"); if (mainfpr) { err = gpg_error (GPG_ERR_INV_DATA); goto leave; } } /* Get the address. */ if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'address' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } address = value; /* FIXME: Check that the "address" matches the User ID we want to * publish. */ /* Get the sender. */ if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item)) && is_valid_mailbox (value))) { log_error ("received invalid wks message: %s\n", "'sender' missing or invalid"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } sender = value; /* FIXME: Check that the "sender" matches the From: address. */ /* Get the nonce. */ if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item)) && strlen (value) > 16)) { log_error ("received invalid wks message: %s\n", "'nonce' missing or too short"); err = gpg_error (GPG_ERR_INV_DATA); goto leave; } nonce = value; /* Send the confirmation. If no key was found, try again without * encryption. */ err = send_confirmation_response (sender, address, nonce, 1, fingerprint); if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) { log_info ("no encryption key found - sending response in the clear\n"); err = send_confirmation_response (sender, address, nonce, 0, NULL); } leave: nvc_release (nvc); return err; } /* Read a confirmation request and decrypt it if needed. This * function may not be used with a mail or MIME message but only with * the actual encrypted or plaintext WKS data. */ static gpg_error_t read_confirmation_request (estream_t msg) { gpg_error_t err; int c; estream_t plaintext = NULL; /* We take a really simple approach to check whether MSG is * encrypted: We know that an encrypted message is always armored * and thus starts with a few dashes. It is even sufficient to * check for a single dash, because that can never be a proper first * WKS data octet. We need to skip leading spaces, though. */ while ((c = es_fgetc (msg)) == ' ' || c == '\t' || c == '\r' || c == '\n') ; if (c == EOF) { log_error ("can't process an empty message\n"); return gpg_error (GPG_ERR_INV_DATA); } if (es_ungetc (c, msg) != c) { log_error ("error ungetting octet from message\n"); return gpg_error (GPG_ERR_INTERNAL); } if (c != '-') err = process_confirmation_request (msg, NULL); else { struct decrypt_stream_parm_s decinfo; err = decrypt_stream (&plaintext, &decinfo, msg); if (err) log_error ("decryption failed: %s\n", gpg_strerror (err)); else if (decinfo.otrust != 'u') { err = gpg_error (GPG_ERR_WRONG_SECKEY); log_error ("key used to decrypt the confirmation request" " was not generated by us (otrust=%c)\n", decinfo.otrust); } else err = process_confirmation_request (plaintext, decinfo.mainfpr); xfree (decinfo.fpr); xfree (decinfo.mainfpr); } es_fclose (plaintext); return err; } /* Called from the MIME receiver to process the plain text data in MSG. */ static gpg_error_t command_receive_cb (void *opaque, const char *mediatype, estream_t msg, unsigned int flags) { gpg_error_t err; (void)opaque; (void)flags; if (!strcmp (mediatype, "application/vnd.gnupg.wks")) err = read_confirmation_request (msg); else { log_info ("ignoring unexpected message of type '%s'\n", mediatype); err = gpg_error (GPG_ERR_UNEXPECTED_MSG); } return err; } /* An object used to communicate with the mirror_one_key callback. */ struct { const char *domain; int anyerror; unsigned int nkeys; /* Number of keys processed. */ unsigned int nuids; /* Number of published user ids. */ } mirror_one_key_parm; /* Return true if the Given a mail DOMAIN and the full addrspec MBOX * match. */ static int domain_matches_mbox (const char *domain, const char *mbox) { const char *s; if (!domain || !mbox) return 0; s = strchr (domain, '@'); if (s) domain = s+1; if (!*domain) return 0; /* Not a valid domain. */ s = strchr (mbox, '@'); if (!s || !s[1]) return 0; /* Not a valid mbox. */ mbox = s+1; return !ascii_strcasecmp (domain, mbox); } /* Core of mirror_one_key with the goal of mirroring just one uid. * UIDLIST is used to figure out whether the given MBOX occurs several * times in UIDLIST and then to single out the newest one. This is * so that for a key with * uid: Joe Someone * uid: Joe * only the news user id (and thus its self-signature) is used. * UIDLIST is nodified to set all MBOX fields to NULL for a processed * user id. FPR is the fingerprint of the key. */ static gpg_error_t mirror_one_keys_userid (estream_t key, const char *mbox, uidinfo_list_t uidlist, const char *fpr) { gpg_error_t err; uidinfo_list_t uid, thisuid, firstuid; time_t thistime; estream_t newkey = NULL; /* Find the UID we want to use. */ thistime = 0; thisuid = firstuid = NULL; for (uid = uidlist; uid; uid = uid->next) { if ((uid->flags & 1) || !uid->mbox || strcmp (uid->mbox, mbox)) continue; /* Already processed or no matching mbox. */ uid->flags |= 1; /* Set "processed" flag. */ if (!firstuid) firstuid = uid; if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = firstuid; /* This is the case for a missing timestamp. */ if (!thisuid) { log_error ("error finding the user id for %s (%s)\n", fpr, mbox); err = gpg_error (GPG_ERR_NO_USER_ID); goto leave; } /* Always filter the key so that the result will be non-armored. */ es_rewind (key); err = wks_filter_uid (&newkey, key, thisuid->uid, 1); if (err) { log_error ("error filtering key %s: %s\n", fpr, gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (opt.add_revocs) { if (es_fseek (newkey, 0, SEEK_END)) { err = gpg_error_from_syserror (); log_error ("error seeking stream: %s\n", gpg_strerror (err)); goto leave; } err = wks_find_add_revocs (newkey, mbox); if (err) { log_error ("error finding revocations for '%s': %s\n", mbox, gpg_strerror (err)); goto leave; } es_rewind (newkey); } err = wks_install_key_core (newkey, mbox); if (opt.verbose) log_info ("key %s published for '%s'\n", fpr, mbox); mirror_one_key_parm.nuids++; if (!opt.quiet && !(mirror_one_key_parm.nuids % 25)) log_info ("%u user ids from %d keys so far\n", mirror_one_key_parm.nuids, mirror_one_key_parm.nkeys); leave: es_fclose (newkey); return err; } /* The callback used by command_mirror. It received an estream with * one key and should return success to process the next key. */ static gpg_error_t mirror_one_key (estream_t key) { gpg_error_t err = 0; char *fpr; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid; const char *domain = mirror_one_key_parm.domain; /* List the key to get all user ids. */ err = wks_list_key (key, &fpr, &uidlist); if (err) { log_error ("error parsing a key: %s - skipped\n", gpg_strerror (err)); mirror_one_key_parm.anyerror = 1; err = 0; goto leave; } for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox || (uid->flags & 1)) continue; /* No mail box or already processed. */ if (uid->expired) continue; if (!domain_matches_mbox (domain, uid->mbox)) continue; /* We don't want this one. */ if (is_in_blacklist (uid->mbox)) continue; err = mirror_one_keys_userid (key, uid->mbox, uidlist, fpr); if (err) { log_error ("error processing key %s: %s - skipped\n", fpr, gpg_strerror (err)); mirror_one_key_parm.anyerror = 1; err = 0; goto leave; } } mirror_one_key_parm.nkeys++; leave: free_uidinfo_list (uidlist); xfree (fpr); return err; } /* Copy the keys from the configured LDAP server into a local WKD. * DOMAINLIST is an array of domain names to restrict the copy to only * the given domains; if it is NULL all keys are mirrored. */ static gpg_error_t command_mirror (char *domainlist[]) { gpg_error_t err; const char *domain; char *domainbuf = NULL; mirror_one_key_parm.anyerror = 0; mirror_one_key_parm.nkeys = 0; mirror_one_key_parm.nuids = 0; if (!domainlist) { mirror_one_key_parm.domain = ""; err = wkd_dirmngr_ks_get (NULL, mirror_one_key); } else { while ((domain = *domainlist++)) { if (*domain != '.' && domain[1] != '@') { /* This does not already specify a mail search by * domain. Change it. */ xfree (domainbuf); domainbuf = xstrconcat (".@", domain, NULL); domain = domainbuf; } mirror_one_key_parm.domain = domain; if (opt.verbose) log_info ("mirroring keys for domain '%s'\n", domain+2); err = wkd_dirmngr_ks_get (domain, mirror_one_key); if (err) break; } } if (!opt.quiet) log_info ("a total of %u user ids from %d keys published\n", mirror_one_key_parm.nuids, mirror_one_key_parm.nkeys); if (err) log_error ("error mirroring LDAP directory: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); else if (mirror_one_key_parm.anyerror) log_info ("warning: errors encountered - not all keys are mirrored\n"); xfree (domainbuf); return err; } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 522ce517b..9738ffe97 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,1676 +1,1676 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 2016 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/status.h" #include "../common/exechelp.h" #ifdef HAVE_W32_SYSTEM #include #endif /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', aListDirs = 'L', aKill = 'K', aReload = 'R', aShowVersions = 'V', aShowConfigs = 'X', oNoVerbose = 500, oHomedir, oBuilddir, oStatusFD, oShowSocket, oChUid, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aLaunch, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, aShowCodepages }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aCheckOptions, "check-options", 256, N_("|COMPONENT|check options") }, { aApplyDefaults, "apply-defaults", 256, N_("apply global default values") }, { aApplyProfile, "apply-profile", 256, N_("|FILE|update configuration files using FILE") }, { aListDirs, "list-dirs", 256, N_("get the configuration directories for @GPGCONF@") }, { aListConfig, "list-config", 256, N_("list global configuration file") }, { aCheckConfig, "check-config", 256, N_("check global configuration file") }, { aQuerySWDB, "query-swdb", 256, N_("query the software version database") }, { aReload, "reload", 256, N_("reload all or a given component")}, { aLaunch, "launch", 256, N_("launch a given component")}, { aKill, "kill", 256, N_("kill a given component")}, { aCreateSocketDir, "create-socketdir", 256, "@"}, { aRemoveSocketDir, "remove-socketdir", 256, "@"}, ARGPARSE_c (aShowVersions, "show-versions", ""), ARGPARSE_c (aShowConfigs, "show-configs", ""), ARGPARSE_c (aShowCodepages, "show-codepages", "@"), { 301, NULL, 0, N_("@\nOptions:\n ") }, { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, - ARGPARSE_s_i (oStatusFD, "status-fd", + ARGPARSE_s_s (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), /* hidden options */ { oHomedir, "homedir", 2, "@" }, { oBuilddir, "build-prefix", 2, "@" }, { oNull, "null", 0, "@" }, { oNoVerbose, "no-verbose", 0, "@"}, ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end(), }; #define CUTLINE_FMT \ "--8<---------------cut here---------------%s------------->8---\n" /* The stream to output the status information. Status Output is disabled if * this is NULL. */ static estream_t statusfp; static void show_versions (estream_t fp); static void show_configs (estream_t fp); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGCONF@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } /* Set the status FD. */ static void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void gpgconf_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } static void list_dirs (estream_t fp, char **names, int special) { static struct { const char *name; const char *(*fnc)(void); const char *extra; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, { "socketdir", gnupg_socketdir, NULL }, { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "keyboxd-socket", keyboxd_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; for (idx = 0; idx < DIM (list); idx++) { s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!names) es_fprintf (fp, "%s:%s\n", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } xfree (tmp); } #ifdef HAVE_W32_SYSTEM tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, "HomeDir"); if (tmp) { int hkcu = 0; int hklm = 0; xfree (tmp); if ((tmp = read_w32_registry_string ("HKEY_CURRENT_USER", GNUPG_REGISTRY_DIR, "HomeDir"))) { xfree (tmp); hkcu = 1; } if ((tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", GNUPG_REGISTRY_DIR, "HomeDir"))) { xfree (tmp); hklm = 1; } es_fflush (fp); if (special) es_fprintf (fp, "\n" "### Note: homedir taken from registry key %s%s\\%s:%s\n" "\n", hkcu?"HKCU":"", hklm?"HKLM":"", GNUPG_REGISTRY_DIR, "HomeDir"); else log_info ("Warning: homedir taken from registry key (%s:%s) in%s%s\n", GNUPG_REGISTRY_DIR, "HomeDir", hkcu?" HKCU":"", hklm?" HKLM":""); } else if ((tmp = read_w32_registry_string (NULL, GNUPG_REGISTRY_DIR, NULL))) { xfree (tmp); es_fflush (fp); if (special) es_fprintf (fp, "\n" "### Note: registry %s without value in HKCU or HKLM\n" "\n", GNUPG_REGISTRY_DIR); else log_info ("Warning: registry key (%s) without value in HKCU or HKLM\n", GNUPG_REGISTRY_DIR); } #else /*!HAVE_W32_SYSTEM*/ (void)special; #endif /*!HAVE_W32_SYSTEM*/ } /* Check whether NAME is valid argument for query_swdb(). Valid names * start with a letter and contain only alphanumeric characters or an * underscore. */ static int valid_swdb_name_p (const char *name) { if (!name || !*name || !alphap (name)) return 0; for (name++; *name; name++) if (!alnump (name) && *name != '_') return 0; return 1; } /* Query the SWDB file. If necessary and possible this functions asks * the dirmngr to load an updated version of that file. The caller * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and * optional the currently installed version in CURRENT_VERSION. The * output written to OUT is a colon delimited line with these fields: * * name :: The name of the package * curvers:: The installed version if given. * status :: This value tells the status of the software package * '-' :: No information available * (error or CURRENT_VERSION not given) * '?' :: Unknown NAME * 'u' :: Update available * 'c' :: The version is Current * 'n' :: The current version is already Newer than the * available one. * urgency :: If the value is greater than zero an urgent update is required. * error :: 0 on success or an gpg_err_code_t * Common codes seen: * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. * GPG_ERR_ENOENT :: The SWDB file is not available. * GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file. * filedate:: Date of the swdb file (yyyymmddThhmmss) * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) * version :: The version string from the swdb. * reldate :: Release date of that version (yyyymmddThhmmss) * size :: Size of the package in bytes. * hash :: SHA-2 hash of the package. * */ static void query_swdb (estream_t out, const char *name, const char *current_version) { gpg_error_t err; const char *search_name; char *fname = NULL; estream_t fp = NULL; char *line = NULL; char *self_version = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; const char *fields[2]; char *p; gnupg_isotime_t filedate = {0}; gnupg_isotime_t verified = {0}; char *value_ver = NULL; gnupg_isotime_t value_date = {0}; char *value_size = NULL; char *value_sha2 = NULL; unsigned long value_size_ul = 0; int status, i; if (!valid_swdb_name_p (name)) { log_error ("error in package name '%s': %s\n", name, gpg_strerror (GPG_ERR_INV_NAME)); goto leave; } if (!strcmp (name, "gnupg")) search_name = GNUPG_SWDB_TAG; else if (!strcmp (name, "gnupg1")) search_name = "gnupg1"; else search_name = name; if (!current_version && !strcmp (name, "gnupg")) { /* Use our own version but string a possible beta string. */ self_version = xstrdup (PACKAGE_VERSION); p = strchr (self_version, '-'); if (p) *p = 0; current_version = self_version; } if (current_version && (strchr (current_version, ':') || compare_version_strings (current_version, NULL))) { log_error ("error in version string '%s': %s\n", current_version, gpg_strerror (GPG_ERR_INV_ARG)); goto leave; } fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (!*filedate && !strcmp (fields[0], ".filedate")) { string2isotime (filedate, fields[1]); continue; } if (!*verified && !strcmp (fields[0], ".verified")) { string2isotime (verified, fields[1]); continue; } /* Tokenize the name. */ p = strrchr (fields[0], '_'); if (!p) continue; /* Name w/o an underscore. */ *p++ = 0; /* Wait for the requested name. */ if (!strcmp (fields[0], search_name)) { if (!strcmp (p, "ver") && !value_ver) value_ver = xstrdup (fields[1]); else if (!strcmp (p, "date") && !*value_date) string2isotime (value_date, fields[1]); else if (!strcmp (p, "size") && !value_size) value_size = xstrdup (fields[1]); else if (!strcmp (p, "sha2") && !value_sha2) value_sha2 = xstrdup (fields[1]); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (!*filedate || !*verified) { err = gpg_error (GPG_ERR_INV_TIME); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); goto leave; } if (!value_ver) { es_fprintf (out, "%s:%s:?:::::::::\n", name, current_version? current_version : ""); goto leave; } if (value_size) { gpg_err_set_errno (0); value_size_ul = strtoul (value_size, &p, 10); if (errno) value_size_ul = 0; else if (*p == 'k') value_size_ul *= 1024; } err = 0; status = '-'; if (compare_version_strings (value_ver, NULL)) err = gpg_error (GPG_ERR_INV_VALUE); else if (!current_version) ; else if (!(i = compare_version_strings (value_ver, current_version))) status = 'c'; else if (i > 0) status = 'u'; else status = 'n'; es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", name, current_version? current_version : "", status, err, filedate, verified, value_ver, value_date, value_size_ul, value_sha2? value_sha2 : ""); leave: xfree (value_ver); xfree (value_size); xfree (value_sha2); xfree (line); es_fclose (fp); xfree (fname); xfree (self_version); } /* gpgconf main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; int show_socket = 0; const char *changeuser = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gc_components_init (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case oStatusFD: - set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); + set_status_fd (translate_sys2libc_fdstr (pargs.r.ret_str, 1)); break; case oShowSocket: show_socket = 1; break; case oChUid: changeuser = pargs.r.ret_str; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aApplyProfile: case aListConfig: case aCheckConfig: case aQuerySWDB: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: case aShowVersions: case aShowConfigs: case aShowCodepages: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) gpgconf_failure (GPG_ERR_USER_2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } fname = argc ? *argv : NULL; /* If requested switch to the requested user or die. */ if (changeuser && (err = gnupg_chuid (changeuser, 0))) gpgconf_failure (err); /* Set the configuraton directories for use by gpgrt_argparser. We * don't have a configuration file for this program but we have code * which reads the component's config files. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) gpgconf_failure (0); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp), 0); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) gpgconf_failure (0); } else { gc_component_kill (-1); } } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else if (cmd == aLaunch) { err = gc_component_launch (idx); if (show_socket) { char *names[2]; if (idx == GC_COMPONENT_GPG_AGENT) names[0] = "agent-socket"; else if (idx == GC_COMPONENT_DIRMNGR) names[0] = "dirmngr-socket"; else if (idx == GC_COMPONENT_KEYBOXD) names[0] = "keyboxd-socket"; else names[0] = NULL; names[1] = NULL; get_outfp (&outfp); list_dirs (outfp, names, 0); } if (err) gpgconf_failure (0); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname || !strcmp (fname, "all")) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) gpgconf_failure (0); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) gpgconf_failure (0); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) gpgconf_failure (0); break; case aApplyProfile: if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) gpgconf_failure (0); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL, 0); break; case aQuerySWDB: /* Query the software version database. */ if (!fname || argc > 2) { es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", GPGCONF_NAME); gpgconf_failure (GPG_ERR_USER_2); } get_outfp (&outfp); query_swdb (outfp, fname, argc > 1? argv[1] : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ xfree (socketdir); socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (gnupg_rmdir (socketdir)) { /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY || gpg_err_code (err) == GPG_ERR_EEXIST) { static const char * const names[] = { GPG_AGENT_SOCK_NAME, GPG_AGENT_EXTRA_SOCK_NAME, GPG_AGENT_BROWSER_SOCK_NAME, GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, KEYBOXD_SOCK_NAME, DIRMNGR_SOCK_NAME }; int i; char *p; for (i=0; i < DIM(names); i++) { p = strconcat (socketdir , "/", names[i], NULL); if (p) gnupg_remove (p); xfree (p); } if (gnupg_rmdir (socketdir)) gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", socketdir, gpg_strerror (err)); else gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } xfree (socketdir); } break; case aShowVersions: { get_outfp (&outfp); show_versions (outfp); } break; case aShowConfigs: { get_outfp (&outfp); show_configs (outfp); } break; case aShowCodepages: #ifdef HAVE_W32_SYSTEM { get_outfp (&outfp); if (GetConsoleCP () != GetConsoleOutputCP ()) es_fprintf (outfp, "Console: CP%u/CP%u\n", GetConsoleCP (), GetConsoleOutputCP ()); else es_fprintf (outfp, "Console: CP%u\n", GetConsoleCP ()); es_fprintf (outfp, "ANSI: CP%u\n", GetACP ()); es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ()); } #endif break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); if (log_get_errorcount (0)) gpgconf_failure (0); else gpgconf_write_status (STATUS_SUCCESS, NULL); return 0; } void gpgconf_failure (gpg_error_t err) { log_flush (); if (!err) err = gpg_error (GPG_ERR_GENERAL); gpgconf_write_status (STATUS_FAILURE, "- %u", gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); } /* Parse the revision part from the extended version blurb. */ static const char * get_revision_from_blurb (const char *blurb, int *r_len) { const char *s = blurb? blurb : ""; int n; for (; *s; s++) if (*s == '\n' && s[1] == '(') break; if (s) { s += 2; for (n=0; s[n] && s[n] != ' '; n++) ; } else { s = "?"; n = 1; } *r_len = n; return s; } static void show_version_gnupg (estream_t fp, const char *prefix) { char *fname, *p; size_t n; estream_t verfp; char line[100]; es_fprintf (fp, "%s%sGnuPG %s (%s)\n%s%s\n", prefix, *prefix?"":"* ", gpgrt_strusage (13), BUILD_REVISION, prefix, gpgrt_strusage (17)); /* Show the GnuPG VS-Desktop version in --show-configs mode */ if (prefix && *prefix == '#') { fname = make_filename (gnupg_bindir (), NULL); n = strlen (fname); if (n > 10 && (!ascii_strcasecmp (fname + n - 10, "/GnuPG/bin") || !ascii_strcasecmp (fname + n - 10, "\\GnuPG\\bin"))) { /* Append VERSION to the ../../ direcory. Note that VERSION * is only 7 bytes and thus fits. */ strcpy (fname + n - 9, "VERSION"); verfp = es_fopen (fname, "r"); if (!verfp) es_fprintf (fp, "%s[VERSION file not found]\n", prefix); else if (!es_fgets (line, sizeof line, verfp)) es_fprintf (fp, "%s[VERSION file is empty]\n", prefix); else { trim_spaces (line); for (p=line; *p; p++) if (*p < ' ' || *p > '~' || *p == '[') *p = '?'; es_fprintf (fp, "%s%s\n", prefix, line); } es_fclose (verfp); } xfree (fname); } #ifdef HAVE_W32_SYSTEM { OSVERSIONINFO osvi = { sizeof (osvi) }; GetVersionEx (&osvi); es_fprintf (fp, "%sWindows %lu.%lu build %lu%s%s%s\n", prefix, (unsigned long)osvi.dwMajorVersion, (unsigned long)osvi.dwMinorVersion, (unsigned long)osvi.dwBuildNumber, *osvi.szCSDVersion? " (":"", osvi.szCSDVersion, *osvi.szCSDVersion? ")":"" ); } #endif /*HAVE_W32_SYSTEM*/ } static void show_version_libgcrypt (estream_t fp) { const char *s; int n; s = get_revision_from_blurb (gcry_check_version ("\x01\x01"), &n); es_fprintf (fp, "* Libgcrypt %s (%.*s)\n", gcry_check_version (NULL), n, s); s = gcry_get_config (0, NULL); if (s) es_fputs (s, fp); } static void show_version_gpgrt (estream_t fp) { const char *s; int n; s = get_revision_from_blurb (gpg_error_check_version ("\x01\x01"), &n); es_fprintf (fp, "* GpgRT %s (%.*s)\n", gpg_error_check_version (NULL), n, s); } /* Printing version information for other libraries is problematic * because we don't want to link gpgconf to all these libraries. The * best solution is delegating this to dirmngr which uses libassuan, * libksba, libnpth and ntbtls anyway. */ static void show_versions_via_dirmngr (estream_t fp) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; gnupg_process_t proc; char *line = NULL; size_t line_len = 0; ssize_t length; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); argv[0] = "--gpgconf-versions"; argv[1] = NULL; err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE, NULL, NULL, &proc); if (err) { log_error ("error spawning %s: %s", pgmname, gpg_strerror (err)); es_fprintf (fp, "[error: can't get further info]\n"); return; } gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL); while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; es_fprintf (fp, "%s\n", line); } if (length < 0 || es_ferror (outfp)) { err = gpg_error_from_syserror (); log_error ("error reading from %s: %s\n", pgmname, gpg_strerror (err)); } if (es_fclose (outfp)) { err = gpg_error_from_syserror (); log_error ("error closing output stream of %s: %s\n", pgmname, gpg_strerror (err)); } err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); log_error ("running %s failed (exitcode=%d): %s\n", pgmname, exitcode, gpg_strerror (err)); es_fprintf (fp, "[error: can't get further info]\n"); } gnupg_process_release (proc); xfree (line); } /* Show all kind of version information. */ static void show_versions (estream_t fp) { show_version_gnupg (fp, ""); es_fputc ('\n', fp); show_version_libgcrypt (fp); es_fputc ('\n', fp); show_version_gpgrt (fp); es_fputc ('\n', fp); show_versions_via_dirmngr (fp); } /* Copy data from file SRC to DST. Returns 0 on success or an error * code on failure. If LISTP is not NULL, that strlist is updated * with the variabale or registry key names detected. Flag bit 0 * indicates a registry entry. */ static gpg_error_t my_copy_file (estream_t src, estream_t dst, strlist_t *listp) { gpg_error_t err; char *line = NULL; size_t line_len = 0; ssize_t length; int written; while ((length = es_read_line (src, &line, &line_len, NULL)) > 0) { /* Strip newline and carriage return, if present. */ written = gpgrt_fwrite (line, 1, length, dst); if (written != length) return gpg_error_from_syserror (); trim_spaces (line); if (*line == '[' && listp) { char **tokens; char *p; for (p=line+1; *p; p++) if (*p != ' ' && *p != '\t') break; if (*p && p[strlen (p)-1] == ']') p[strlen (p)-1] = 0; tokens = strtokenize (p, " \t"); if (!tokens) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); return err; } /* Check whether we have a getreg or getenv statement and * store the third token to later retrieval. */ if (tokens[0] && tokens[1] && tokens[2] && (!strcmp (tokens[0], "getreg") || !strcmp (tokens[0], "getenv"))) { int isreg = (tokens[0][3] == 'r'); strlist_t sl = *listp; for (sl = *listp; sl; sl = sl->next) if (!strcmp (sl->d, tokens[2]) && (sl->flags & 1) == isreg) break; if (!sl) /* Not yet in the respective list. */ { sl = add_to_strlist (listp, tokens[2]); if (isreg) sl->flags = 1; } } xfree (tokens); } } if (length < 0 || es_ferror (src)) return gpg_error_from_syserror (); if (gpgrt_fflush (dst)) return gpg_error_from_syserror (); return 0; } /* Helper for show_configs */ static void show_configs_one_file (const char *fname, int global, estream_t outfp, strlist_t *listp) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (outfp, "###\n### %s config \"%s\": %s\n###\n", global? "global":"local", fname, (gpg_err_code (err) == GPG_ERR_ENOENT)? "not installed" : gpg_strerror (err)); } else { es_fprintf (outfp, "###\n### %s config \"%s\"\n###\n", global? "global":"local", fname); es_fprintf (outfp, CUTLINE_FMT, "start"); err = my_copy_file (fp, outfp, listp); if (err) log_error ("error copying file \"%s\": %s\n", fname, gpg_strerror (err)); es_fprintf (outfp, CUTLINE_FMT, "end--"); es_fclose (fp); } } #ifdef HAVE_W32_SYSTEM /* Print registry entries relevant to the GnuPG system and related * software. */ static void show_other_registry_entries (estream_t outfp) { static struct { int group; const char *name; } names[] = { { 1, "HKLM\\Software\\Gpg4win:Install Directory" }, { 1, "HKLM\\Software\\Gpg4win:Desktop-Version" }, { 1, "HKLM\\Software\\Gpg4win:VS-Desktop-Version" }, { 1, "\\" GNUPG_REGISTRY_DIR ":HomeDir" }, { 1, "\\" GNUPG_REGISTRY_DIR ":DefaultLogFile" }, { 2, "\\Software\\Microsoft\\Office\\Outlook\\Addins\\GNU.GpgOL" ":LoadBehavior" }, { 2, "HKCU\\Software\\Microsoft\\Office\\16.0\\Outlook\\Options\\Mail:" "ReadAsPlain" }, { 2, "HKCU\\Software\\Policies\\Microsoft\\Office\\16.0\\Outlook\\" "Options\\Mail:ReadAsPlain" }, { 3, "logFile" }, { 3, "enableDebug" }, { 3, "searchSmimeServers" }, { 3, "smimeInsecureReplyAllowed" }, { 3, "enableSmime" }, { 3, "preferSmime" }, { 3, "encryptDefault" }, { 3, "signDefault" }, { 3, "inlinePGP" }, { 3, "replyCrypt" }, { 3, "autoresolve" }, { 3, "autoretrieve" }, { 3, "automation" }, { 3, "autosecure" }, { 3, "autotrust" }, { 3, "autoencryptUntrusted" }, { 3, "autoimport" }, { 3, "splitBCCMails" }, { 3, "combinedOpsEnabled" }, { 3, "encryptSubject" }, { 0, NULL } }; int idx; int group = 0; char *namebuf = NULL; const char *name; int from_hklm; for (idx=0; (name = names[idx].name); idx++) { char *value; if (names[idx].group == 3) { xfree (namebuf); namebuf = xstrconcat ("\\Software\\GNU\\GpgOL", ":", names[idx].name, NULL); name = namebuf; } value = read_w32_reg_string (name, &from_hklm); if (!value) continue; if (names[idx].group != group) { group = names[idx].group; es_fprintf (outfp, "###\n### %s related:\n", group == 1 ? "GnuPG Desktop" : group == 2 ? "Outlook" : group == 3 ? "\\Software\\GNU\\GpgOL" : "System" ); } if (group == 3) es_fprintf (outfp, "### %s=%s%s\n", names[idx].name, value, from_hklm? " [hklm]":""); else es_fprintf (outfp, "### %s\n### ->%s<-%s\n", name, value, from_hklm? " [hklm]":""); xfree (value); } es_fprintf (outfp, "###\n"); xfree (namebuf); } /* Print registry entries take from a configuration file. */ static void show_registry_entries_from_file (estream_t outfp) { gpg_error_t err; char *fname; estream_t fp; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char *value = NULL; int from_hklm; int any = 0; fname = make_filename (gnupg_datadir (), "gpgconf.rnames", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("error opening '%s': %s\n", fname, gpg_strerror (err)); goto leave; } maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } trim_spaces (line); if (*line == '#') continue; xfree (value); value = read_w32_reg_string (line, &from_hklm); if (!value) continue; if (!any) { any = 1; es_fprintf (outfp, "### Taken from gpgconf.rnames:\n"); } es_fprintf (outfp, "### %s\n### ->%s<-%s\n", line, value, from_hklm? " [hklm]":""); } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); } leave: if (any) es_fprintf (outfp, "###\n"); xfree (value); xfree (line); es_fclose (fp); xfree (fname); } #endif /*HAVE_W32_SYSTEM*/ /* Show all config files. */ static void show_configs (estream_t outfp) { static const char *names[] = { "common.conf", "gpg-agent.conf", "scdaemon.conf", "dirmngr.conf", "gpg.conf", "gpgsm.conf" }; static const char *envvars[] = { "PATH", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "LD_LIBRARY_PATH", "LD_PRELOAD", "LD_AUDIT", "LD_ORIGIN_PATH" }; gpg_error_t err; int idx; char *fname; gnupg_dir_t dir; gnupg_dirent_t dir_entry; size_t n; int any; strlist_t list = NULL; strlist_t sl; const char *s; int got_gpgconfconf = 0; es_fprintf (outfp, "### Dump of all standard config files\n"); show_version_gnupg (outfp, "### "); es_fprintf (outfp, "### Libgcrypt %s\n", gcry_check_version (NULL)); es_fprintf (outfp, "### GpgRT %s\n", gpg_error_check_version (NULL)); #ifdef HAVE_W32_SYSTEM es_fprintf (outfp, "### Codepages:"); if (GetConsoleCP () != GetConsoleOutputCP ()) es_fprintf (outfp, " %u/%u", GetConsoleCP (), GetConsoleOutputCP ()); else es_fprintf (outfp, " %u", GetConsoleCP ()); es_fprintf (outfp, " %u", GetACP ()); es_fprintf (outfp, " %u\n", GetOEMCP ()); #endif es_fprintf (outfp, "###\n\n"); list_dirs (outfp, NULL, 1); es_fprintf (outfp, "\n"); for (idx=0; idx < DIM(envvars); idx++) if ((s = getenv (envvars[idx]))) es_fprintf (outfp, "%s=%s\n", envvars[idx], s); es_fprintf (outfp, "\n"); fname = make_filename (gnupg_sysconfdir (), "gpgconf.conf", NULL); if (!gnupg_access (fname, F_OK)) { got_gpgconfconf = 1; show_configs_one_file (fname, 1, outfp, &list); es_fprintf (outfp, "\n"); } xfree (fname); for (idx = 0; idx < DIM (names); idx++) { fname = make_filename (gnupg_sysconfdir (), names[idx], NULL); show_configs_one_file (fname, 1, outfp, &list); xfree (fname); fname = make_filename (gnupg_homedir (), names[idx], NULL); show_configs_one_file (fname, 0, outfp, &list); xfree (fname); es_fprintf (outfp, "\n"); } /* Print the encountered registry values and envvars. */ if (list) { any = 0; for (sl = list; sl; sl = sl->next) if (!(sl->flags & 1)) { if (!any) { any = 1; es_fprintf (outfp, "###\n" "### List of encountered environment variables:\n"); } if ((s = getenv (sl->d))) es_fprintf (outfp, "### %-12s ->%s<-\n", sl->d, s); else es_fprintf (outfp, "### %-12s [not set]\n", sl->d); } if (any) es_fprintf (outfp, "###\n"); } #ifdef HAVE_W32_SYSTEM es_fprintf (outfp, "###\n### Registry entries:\n"); any = 0; if (list) { for (sl = list; sl; sl = sl->next) if ((sl->flags & 1)) { char *p; int from_hklm; if (!any) { any = 1; es_fprintf (outfp, "###\n### Encountered in config files:\n"); } if ((p = read_w32_reg_string (sl->d, &from_hklm))) es_fprintf (outfp, "### %s ->%s<-%s\n", sl->d, p, from_hklm? " [hklm]":""); else es_fprintf (outfp, "### %s [not set]\n", sl->d); xfree (p); } } if (!any) es_fprintf (outfp, "###\n"); show_other_registry_entries (outfp); show_registry_entries_from_file (outfp); #endif /*HAVE_W32_SYSTEM*/ free_strlist (list); any = 0; /* Additional warning. */ if (got_gpgconfconf) { es_fprintf (outfp, "###\n" "### Warning: legacy config file \"gpgconf.conf\" found\n"); any = 1; } /* Check for uncommon files in the home directory. */ dir = gnupg_opendir (gnupg_homedir ()); if (!dir) { err = gpg_error_from_syserror (); log_error ("error reading directory \"%s\": %s\n", gnupg_homedir (), gpg_strerror (err)); return; } while ((dir_entry = gnupg_readdir (dir))) { for (idx = 0; idx < DIM (names); idx++) { n = strlen (names[idx]); if (!ascii_strncasecmp (dir_entry->d_name, names[idx], n) && dir_entry->d_name[n] == '-' && ascii_strncasecmp (dir_entry->d_name, "gpg.conf-1", 10)) { if (!any) { any = 1; es_fprintf (outfp, "###\n" "### Warning: suspicious files in \"%s\":\n", gnupg_homedir ()); } es_fprintf (outfp, "### %s\n", dir_entry->d_name); } } } if (any) es_fprintf (outfp, "###\n"); gnupg_closedir (dir); } diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index e6f5b55a2..99da9ecf0 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,1389 +1,1395 @@ /* gpgtar-create.c - Create a TAR archive * Copyright (C) 2016-2017, 2019-2023 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif /*!HAVE_W32_SYSTEM*/ #include "../common/i18n.h" #include #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "../common/membuf.h" #include "gpgtar.h" #ifndef HAVE_LSTAT #define lstat(a,b) gnupg_stat ((a), (b)) #endif /* Number of files to be write. */ static unsigned long global_total_files; /* Count the number of written file and thus headers. Extended * headers are not counted. */ static unsigned long global_written_files; /* Total data expected to be written. */ static unsigned long long global_total_data; /* Number of data bytes written so far. */ static unsigned long long global_written_data; /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; unsigned long file_count; int nestlevel; }; /* See ../g10/progress.c:write_status_progress for some background. */ static void write_progress (int countmode, unsigned long long current, unsigned long long total_arg) { char units[] = "BKMGTPEZY?"; int unitidx = 0; uint64_t total = total_arg; if (!opt.status_stream) return; /* Not enabled. */ if (countmode) { if (total && current > total) current = total; } else if (total) /* Size mode: This may use units. */ { if (current > total) current = total; while (total > 1024*1024) { total /= 1024; current /= 1024; unitidx++; } } else /* Size mode */ { while (current > 1024*1024) { current /= 1024; unitidx++; } } if (unitidx > sizeof units - 1) unitidx = sizeof units - 1; if (countmode) es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar c %zu %zu\n", (size_t)current, (size_t)total); else es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar s %zu %zu %c%s\n", (size_t)current, (size_t)total, units[unitidx], unitidx? "iB" : ""); } /* On Windows convert name to UTF8 and return it; caller must release * the result. On Unix or if ALREADY_UTF8 is set, this function is a * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ static char * name_to_utf8 (const char *name, int already_utf8) { #ifdef HAVE_W32_SYSTEM wchar_t *wstring; char *result; if (already_utf8) result = xtrystrdup (name); else { wstring = native_to_wchar (name); if (!wstring) return NULL; result = wchar_to_utf8 (wstring); xfree (wstring); } return result; #else /*!HAVE_W32_SYSTEM */ (void)already_utf8; return xtrystrdup (name); #endif /*!HAVE_W32_SYSTEM */ } /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the W32 version. */ #ifdef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_w32 (tar_header_t hdr) { char *p; wchar_t *wfname; WIN32_FILE_ATTRIBUTE_DATA fad; DWORD attr; for (p=hdr->name; *p; p++) if (*p == '/') *p = '\\'; wfname = gpgrt_fname_to_wchar (hdr->name); for (p=hdr->name; *p; p++) if (*p == '\\') *p = '/'; if (!wfname) { log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); return gpg_error_from_syserror (); } if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) { log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); xfree (wfname); return gpg_error_from_syserror (); } xfree (wfname); attr = fad.dwFileAttributes; if ((attr & FILE_ATTRIBUTE_NORMAL)) hdr->typeflag = TF_REGULAR; else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->typeflag = TF_DIRECTORY; else if ((attr & FILE_ATTRIBUTE_DEVICE)) hdr->typeflag = TF_NOTSUP; else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) hdr->typeflag = TF_NOTSUP; else hdr->typeflag = TF_REGULAR; /* Map some attributes to USTAR defined mode bits. */ hdr->mode = 0640; /* User may read and write, group only read. */ if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->mode |= 0110; /* Dirs are user and group executable. */ if ((attr & FILE_ATTRIBUTE_READONLY)) hdr->mode &= ~0200; /* Clear the user write bit. */ if ((attr & FILE_ATTRIBUTE_HIDDEN)) hdr->mode &= ~0707; /* Clear all user and other bits. */ if ((attr & FILE_ATTRIBUTE_SYSTEM)) hdr->mode |= 0004; /* Make it readable by other. */ /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = (fad.nFileSizeHigh * ((unsigned long long)MAXDWORD+1) + fad.nFileSizeLow); hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) | fad.ftLastWriteTime.dwLowDateTime); if (!hdr->mtime) hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) | fad.ftCreationTime.dwLowDateTime); hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the POSIX version. */ #ifndef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_posix (tar_header_t hdr) { gpg_error_t err; struct stat sbuf; if (lstat (hdr->name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; return 0; } #endif /*!HAVE_W32_SYSTEM*/ /* Add a new entry. The name of a directory entry is ENTRYNAME; if that is NULL, DNAME is the name of the directory itself. Under Windows ENTRYNAME shall have backslashes replaced by standard slashes. */ static gpg_error_t add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; size_t dnamelen = strlen (dname); log_assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + (entryname? strlen (entryname) : 0) + 1); if (!hdr) return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM err = fillup_entry_w32 (hdr); #else err = fillup_entry_posix (hdr); #endif if (err) xfree (hdr); else { /* FIXME: We don't have the extended info yet available so we * can't print them. */ if (opt.verbose) gpgtar_print_header (hdr, NULL, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; scanctrl->file_count++; /* Print a progress line during scnanning in increments of 5000 * and not of 100 as we doing during write: Scanning is of * course much faster. */ if (!(scanctrl->file_count % 5000)) write_progress (1, scanctrl->file_count, 0); } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; #ifdef HAVE_W32_SYSTEM /* Note that we introduced gnupg_opendir only after we had deployed * this code and thus we don't change it for now. */ WIN32_FIND_DATAW fi; HANDLE hd = INVALID_HANDLE_VALUE; char *p; if (!*dname) return 0; /* An empty directory name has no entries. */ { char *fname; wchar_t *wfname; fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if (!strcmp (dname, "/")) strcpy (fname, "/*"); /* Trailing slash is not allowed. */ else if (!strcmp (dname, ".")) strcpy (fname, "*"); else if (*dname && dname[strlen (dname)-1] == '/') strcpy (stpcpy (fname, dname), "*"); else if (*dname && dname[strlen (dname)-1] != '*') strcpy (stpcpy (fname, dname), "/*"); else strcpy (fname, dname); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; wfname = gpgrt_fname_to_wchar (fname); xfree (fname); if (!wfname) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); goto leave; } hd = FindFirstFileW (wfname, &fi); if (hd == INVALID_HANDLE_VALUE) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); xfree (wfname); goto leave; } xfree (wfname); } do { char *fname = wchar_to_utf8 (fi.cFileName); if (!fname) { err = gpg_error_from_syserror (); log_error ("error converting filename: %s\n", w32_strerror (-1)); break; } for (p=fname; *p; p++) if (*p == '\\') *p = '/'; if (!strcmp (fname, "." ) || !strcmp (fname, "..")) err = 0; /* Skip self and parent dir entry. */ else if (!strncmp (dname, "./", 2) && dname[2]) err = add_entry (dname+2, fname, scanctrl); else err = add_entry (dname, fname, scanctrl); xfree (fname); } while (!err && FindNextFileW (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); } leave: if (hd != INVALID_HANDLE_VALUE) FindClose (hd); #else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); #endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; log_assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory '%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' #ifdef HAVE_DOSISH_SYSTEM || *pattern == '\\' #endif ) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; log_assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { #ifdef HAVE_W32_SYSTEM mem2str (lastuname, uid? "user":"root", sizeof lastuname); #else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } #endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { #ifdef HAVE_W32_SYSTEM mem2str (lastgname, gid? "users":"root", sizeof lastgname); #else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } #endif } mem2str (buffer, lastgname, length); } static void compute_checksum (void *record) { struct ustar_raw_header *raw = record; unsigned long chksum = 0; unsigned char *p; size_t n; memset (raw->checksum, ' ', sizeof raw->checksum); p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; } /* Read a symlink without truncating it. Caller must release the * returned buffer. Returns NULL on error. */ #ifndef HAVE_W32_SYSTEM static char * myreadlink (const char *name) { char *buffer; size_t size; int nread; for (size = 1024; size <= 65536; size *= 2) { buffer = xtrymalloc (size); if (!buffer) return NULL; nread = readlink (name, buffer, size - 1); if (nread < 0) { xfree (buffer); return NULL; } if (nread < size - 1) { buffer[nread] = 0; return buffer; /* Got it. */ } xfree (buffer); } gpg_err_set_errno (ERANGE); return NULL; } #endif /*Unix*/ /* Build a header. If the filename or the link name ist too long * allocate an exthdr and use a replacement file name in RECORD. * Caller should always release R_EXTHDR; this function initializes it * to point to NULL. */ static gpg_error_t build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; strlist_t sl; memset (record, 0, RECORDSIZE); *r_exthdr = NULL; /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { /* Too long - prepare extended header. */ sl = add_to_strlist_try (r_exthdr, hdr->name); if (!sl) { err = gpg_error_from_syserror (); log_error ("error storing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl->flags = 1; /* Mark as path */ /* The name we use is not POSIX compliant but because we * expect that (for security issues) a tarball will anyway * be extracted to a unique new directory, a simple counter * will do. To ease testing we also put in the PID. The * count is bumped after the header has been written. */ snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu", (unsigned int)getpid(), global_written_files + 1); } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); #ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; char *p; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; if (nread == sizeof raw->linkname -1) { /* Truncated - read again and store as extended header. */ p = myreadlink (hdr->name); if (!p) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl = add_to_strlist_try (r_exthdr, p); xfree (p); if (!sl) { err = gpg_error_from_syserror (); log_error ("error storing syslink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl->flags = 2; /* Mark as linkpath */ } } #endif /*!HAVE_W32_SYSTEM*/ compute_checksum (record); return 0; } /* Add an extended header record (NAME,VALUE) to the buffer MB. */ static void add_extended_header_record (membuf_t *mb, const char *name, const char *value) { size_t n, n0, n1; char numbuf[35]; size_t valuelen; /* To avoid looping in most cases, we guess the initial value. */ valuelen = strlen (value); n1 = valuelen > 95? 3 : 2; do { n0 = n1; /* (3 for the space before name, the '=', and the LF.) */ n = n0 + strlen (name) + valuelen + 3; snprintf (numbuf, sizeof numbuf, "%zu", n); n1 = strlen (numbuf); } while (n0 != n1); put_membuf_str (mb, numbuf); put_membuf (mb, " ", 1); put_membuf_str (mb, name); put_membuf (mb, "=", 1); put_membuf (mb, value, valuelen); put_membuf (mb, "\n", 1); } /* Write the extended header specified by EXTHDR to STREAM. */ static gpg_error_t write_extended_header (estream_t stream, const void *record, strlist_t exthdr) { gpg_error_t err = 0; struct ustar_raw_header raw; strlist_t sl; membuf_t mb; char *buffer, *p; size_t buflen; init_membuf (&mb, 2*RECORDSIZE); for (sl=exthdr; sl; sl = sl->next) { if (sl->flags == 1) add_extended_header_record (&mb, "path", sl->d); else if (sl->flags == 2) add_extended_header_record (&mb, "linkpath", sl->d); } buffer = get_membuf (&mb, &buflen); if (!buffer) { err = gpg_error_from_syserror (); log_error ("error building extended header: %s\n", gpg_strerror (err)); goto leave; } /* We copy the header from the standard header record, so that an * extracted extended header (using a non-pax aware software) is * written with the same properties as the original file. The real * entry will overwrite it anyway. Of course we adjust the size and * the type. */ memcpy (&raw, record, RECORDSIZE); store_xoctal (raw.size, sizeof raw.size, buflen); raw.typeflag[0] = 'x'; /* Mark as extended header. */ compute_checksum (&raw); err = write_record (stream, &raw); if (err) goto leave; for (p = buffer; buflen >= RECORDSIZE; p += RECORDSIZE, buflen -= RECORDSIZE) { err = write_record (stream, p); if (err) goto leave; } if (buflen) { /* Reuse RAW for builidng the last record. */ memcpy (&raw, p, buflen); memset ((char*)&raw+buflen, 0, RECORDSIZE - buflen); err = write_record (stream, &raw); if (err) goto leave; } leave: xfree (buffer); return err; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr, unsigned int *skipped_open) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; strlist_t exthdr = NULL; int any; err = build_header (record, hdr, &exthdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("silently skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb,sysopen"); if (!infp) { err = gpg_error_from_syserror (); log_info ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); ++*skipped_open; if (!*skipped_open) /* Protect against overflow. */ --*skipped_open; return 0; } } else infp = NULL; if (exthdr && (err = write_extended_header (stream, record, exthdr))) goto leave; err = write_record (stream, record); if (err) goto leave; global_written_files++; if (!(global_written_files % 100)) write_progress (1, global_written_files, global_total_files); if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); if (!nbytes) nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file '%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } else if (nbytes < RECORDSIZE) memset (record + nbytes, 0, RECORDSIZE - nbytes); any = 1; err = write_record (stream, record); if (err) goto leave; global_written_data += nbytes; if (!((global_written_data/nbytes) % (2048*100))) write_progress (0, global_written_data, global_total_data); } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file '%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); free_strlist (exthdr); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } /* Create a new tarball using the names in the array INPATTERN. If INPATTERN is NULL take the pattern as null terminated strings from stdin or from the file specified by FILES_FROM. If NULL_NAMES is set the filenames in such a file are delimited by a binary Nul and not by a LF. */ gpg_error_t gpgtar_create (char **inpattern, const char *files_from, int null_names, int encrypt, int sign) { gpg_error_t err = 0; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; estream_t files_from_stream = NULL; estream_t outstream = NULL; int eof_seen = 0; gnupg_process_t proc = NULL; unsigned int skipped_open = 0; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; if (!inpattern) { if (!files_from || !strcmp (files_from, "-")) { files_from = "-"; files_from_stream = es_stdin; if (null_names) es_set_binary (es_stdin); } else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", files_from, gpg_strerror (err)); return err; } } if (opt.directory && gnupg_chdir (opt.directory)) { err = gpg_error_from_syserror (); log_error ("chdir to '%s' failed: %s\n", opt.directory, gpg_strerror (err)); return err; } while (!eof_seen) { char *pat, *p; int skip_this = 0; if (inpattern) { const char *pattern = *inpattern; if (!pattern) break; /* End of array. */ inpattern++; if (!*pattern) continue; pat = name_to_utf8 (pattern, 0); } else /* Read Nul or LF delimited pattern from files_from_stream. */ { int c; char namebuf[4096]; size_t n = 0; for (;;) { if ((c = es_getc (files_from_stream)) == EOF) { if (es_ferror (files_from_stream)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", files_from, gpg_strerror (err)); goto leave; } c = null_names ? 0 : '\n'; eof_seen = 1; } if (n >= sizeof namebuf - 1) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename too long"); } } else namebuf[n++] = c; if (null_names) { if (!c) { namebuf[n] = 0; break; } } else /* Shall be LF delimited. */ { if (!c) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename with embedded Nul"); } } else if ( c == '\n' ) { namebuf[n] = 0; ascii_trim_spaces (namebuf); n = strlen (namebuf); break; } } } if (skip_this || n < 2) continue; pat = name_to_utf8 (namebuf, opt.utf8strings); } if (!pat) { err = gpg_error_from_syserror (); log_error ("memory allocation problem: %s\n", gpg_strerror (err)); goto leave; } for (p=pat; *p; p++) if (*p == '\\') *p = '/'; if (opt.verbose > 1) log_info ("scanning '%s'\n", pat); start_tail = scanctrl->flist_tail; if (skip_this || !pattern_valid_p (pat)) log_error ("skipping invalid name '%s'\n", pat); else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) scan_recursive (pat, scanctrl); xfree (pat); } if (files_from_stream && files_from_stream != es_stdin) es_fclose (files_from_stream); global_total_files = global_total_data = 0; global_written_files = global_written_data = 0; for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { global_total_files++; global_total_data += hdr->size; } write_progress (1, 0, global_total_files); write_progress (0, 0, global_total_data); if (encrypt || sign) { strlist_t arg; ccparray_t ccp; int except[2] = { -1, -1 }; const char **argv; /* '--encrypt' may be combined with '--symmetric', but 'encrypt' * is set either way. Clear it if no recipients are specified. */ if (opt.symmetric && opt.recipients == NULL) encrypt = 0; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.answer_yes) ccparray_put (&ccp, "--yes"); if (opt.answer_no) ccparray_put (&ccp, "--no"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); - if (opt.status_fd != -1) + if (opt.status_fd) { static char tmpbuf[40]; + es_syshd_t hd; - snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); - except[0] = opt.status_fd; + es_syshd (opt.status_stream, &hd); +#ifdef HAVE_W32_SYSTEM + except[0] = hd.u.handle; +#else + except[0] = hd.u.fd; +#endif } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, opt.outfile? opt.outfile : "-"); if (encrypt) ccparray_put (&ccp, "--encrypt"); if (sign) ccparray_put (&ccp, "--sign"); if (opt.user) { ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, opt.user); } if (opt.symmetric) ccparray_put (&ccp, "--symmetric"); for (arg = opt.recipients; arg; arg = arg->next) { ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, arg->d); } for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (opt.gpg_program, argv, (GNUPG_PROCESS_STDIN_PIPE | GNUPG_PROCESS_STDOUT_KEEP | GNUPG_PROCESS_STDERR_KEEP), gnupg_spawn_helper, except, &proc); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, &outstream, NULL, NULL); es_set_binary (outstream); } else if (opt.outfile) /* No crypto */ { if (!strcmp (opt.outfile, "-")) outstream = es_stdout; else outstream = es_fopen (opt.outfile, "wb,sysopen"); if (!outstream) { err = gpg_error_from_syserror (); goto leave; } if (outstream == es_stdout) es_set_binary (es_stdout); } else /* Also no crypto. */ { outstream = es_stdout; es_set_binary (outstream); } skipped_open = 0; for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr, &skipped_open); if (err) goto leave; } err = write_eof_mark (outstream); if (err) goto leave; write_progress (1, global_written_files, global_total_files); write_progress (0, global_written_data, global_total_data); if (proc) { err = es_fclose (outstream); outstream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } if (skipped_open) { log_info ("number of skipped files: %u\n", skipped_open); log_error ("exiting with failure status due to previous errors\n"); } leave: if (!err) { gpg_error_t first_err; if (outstream != es_stdout) first_err = es_fclose (outstream); else first_err = es_fflush (outstream); outstream = NULL; if (! err) err = first_err; } if (err) { log_error ("creating tarball '%s' failed: %s\n", opt.outfile ? opt.outfile : "-", gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } return err; } diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index be483f87c..33b88ff4d 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -1,534 +1,540 @@ /* gpgtar-extract.c - Extract from a TAR archive * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include "../common/i18n.h" #include #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "gpgtar.h" static gpg_error_t check_suspicious_name (const char *name, tarinfo_t info) { size_t n; n = strlen (name); #ifdef HAVE_DOSISH_SYSTEM if (strchr (name, '\\')) { log_error ("filename '%s' contains a backslash - " "can't extract on this system\n", name); info->skipped_badname++; return gpg_error (GPG_ERR_INV_NAME); } #endif /*HAVE_DOSISH_SYSTEM*/ if (!n || strstr (name, "//") || strstr (name, "/../") || !strncmp (name, "../", 3) || (n >= 3 && !strcmp (name+n-3, "/.." ))) { log_error ("filename '%s' has suspicious parts - not extracting\n", name); info->skipped_suspicious++; return gpg_error (GPG_ERR_INV_NAME); } return 0; } static gpg_error_t extract_regular (estream_t stream, const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; char record[RECORDSIZE]; size_t n, nbytes, nwritten; char *fname_buffer = NULL; const char *fname; estream_t outfp = NULL; strlist_t sl; fname = hdr->name; for (sl = exthdr; sl; sl = sl->next) if (sl->flags == 1) fname = sl->d; err = check_suspicious_name (fname, info); if (err) goto leave; fname_buffer = strconcat (dirname, "/", fname, NULL); if (!fname_buffer) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } fname = fname_buffer; if (opt.dry_run) outfp = es_fopen ("/dev/null", "wb"); else outfp = es_fopen (fname, "wb,sysopen"); if (!outfp) { err = gpg_error_from_syserror (); log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); goto leave; } for (n=0; n < hdr->nrecords;) { err = read_record (stream, record); if (err) goto leave; info->nblocks++; n++; if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) nbytes = RECORDSIZE; else nbytes = (hdr->size % RECORDSIZE); nwritten = es_fwrite (record, 1, nbytes, outfp); if (nwritten != nbytes) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* Fixme: Set permissions etc. */ leave: if (!err) { if (opt.verbose) log_info ("extracted '%s'\n", fname); info->nextracted++; } es_fclose (outfp); if (err && fname && outfp) { if (gnupg_remove (fname)) log_error ("error removing incomplete file '%s': %s\n", fname, gpg_strerror (gpg_error_from_syserror ())); } xfree (fname_buffer); return err; } static gpg_error_t extract_directory (const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; const char *name; char *fname = NULL; strlist_t sl; name = hdr->name; for (sl = exthdr; sl; sl = sl->next) if (sl->flags == 1) name = sl->d; err = check_suspicious_name (name, info); if (err) goto leave; fname = strconcat (dirname, "/", name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } /* Remove a possible trailing slash. */ if (fname[strlen (fname)-1] == '/') fname[strlen (fname)-1] = 0; if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------")) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EEXIST) { /* Ignore existing directories while extracting. */ err = 0; } if (gpg_err_code (err) == GPG_ERR_ENOENT) { /* Try to create the directory with parents but keep the original error code in case of a failure. */ int rc = 0; char *p; size_t prefixlen; /* (PREFIXLEN is the length of the new directory we use to * extract the tarball.) */ prefixlen = strlen (dirname) + 1; for (p = fname+prefixlen; (p = strchr (p, '/')); p++) { *p = 0; rc = gnupg_mkdir (fname, "-rwx------"); if (gpg_err_code (rc) == GPG_ERR_EEXIST) rc = 0; *p = '/'; if (rc) break; } if (!rc && !gnupg_mkdir (fname, "-rwx------")) err = 0; } if (err) log_error ("error creating directory '%s': %s\n", fname, gpg_strerror (err)); } leave: if (!err && opt.verbose) log_info ("created '%s/'\n", fname); xfree (fname); return err; } static gpg_error_t extract (estream_t stream, const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; size_t n; if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) err = extract_regular (stream, dirname, info, hdr, exthdr); else if (hdr->typeflag == TF_DIRECTORY) err = extract_directory (dirname, info, hdr, exthdr); else { char record[RECORDSIZE]; log_info ("unsupported file type %d for '%s' - skipped\n", (int)hdr->typeflag, hdr->name); if (hdr->typeflag == TF_SYMLINK) info->skipped_symlinks++; else if (hdr->typeflag == TF_HARDLINK) info->skipped_hardlinks++; else info->skipped_other++; for (err = 0, n=0; !err && n < hdr->nrecords; n++) { err = read_record (stream, record); if (!err) info->nblocks++; } } return err; } /* Create a new directory to be used for extracting the tarball. Returns the name of the directory which must be freed by the caller. In case of an error a diagnostic is printed and NULL returned. */ static char * create_directory (const char *dirprefix) { gpg_error_t err = 0; char *prefix_buffer = NULL; char *dirname = NULL; size_t n; int idx; /* Remove common suffixes. */ n = strlen (dirprefix); if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG) || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e"))) { prefix_buffer = xtrystrdup (dirprefix); if (!prefix_buffer) { err = gpg_error_from_syserror (); goto leave; } prefix_buffer[n-4] = 0; dirprefix = prefix_buffer; } for (idx=1; idx < 5000; idx++) { xfree (dirname); dirname = xtryasprintf ("%s_%d_", dirprefix, idx); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } if (!gnupg_mkdir (dirname, "-rwx------")) goto leave; /* Ready. */ if (errno != EEXIST && errno != ENOTDIR) { err = gpg_error_from_syserror (); goto leave; } } err = gpg_error (GPG_ERR_LIMIT_REACHED); leave: if (err) { log_error ("error creating an extract directory: %s\n", gpg_strerror (err)); xfree (dirname); dirname = NULL; } xfree (prefix_buffer); return dirname; } gpg_error_t gpgtar_extract (const char *filename, int decrypt) { gpg_error_t err; estream_t stream = NULL; tar_header_t header = NULL; strlist_t extheader = NULL; const char *dirprefix = NULL; char *dirname = NULL; struct tarinfo_s tarinfo_buffer; tarinfo_t tarinfo = &tarinfo_buffer; gnupg_process_t proc; char *logfilename = NULL; unsigned long long notextracted; memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (opt.directory) dirname = xtrystrdup (opt.directory); else { if (opt.filename) { dirprefix = strrchr (opt.filename, '/'); if (dirprefix) dirprefix++; else dirprefix = opt.filename; } else if (filename) { dirprefix = strrchr (filename, '/'); if (dirprefix) dirprefix++; else dirprefix = filename; } if (!dirprefix || !*dirprefix) dirprefix = "GPGARCH"; dirname = create_directory (dirprefix); if (!dirname) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } } if (opt.verbose) log_info ("extracting to '%s/'\n", dirname); if (decrypt) { strlist_t arg; ccparray_t ccp; int except[2] = { -1, -1 }; const char **argv; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); - if (opt.status_fd != -1) + if (opt.status_fd) { static char tmpbuf[40]; + es_syshd_t hd; - snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); - except[0] = opt.status_fd; + es_syshd (opt.status_stream, &hd); +#ifdef HAVE_W32_SYSTEM + except[0] = hd.u.handle; +#else + except[0] = hd.u.fd; +#endif } if (opt.with_log) { ccparray_put (&ccp, "--log-file"); logfilename = xstrconcat (dirname, ".log", NULL); ccparray_put (&ccp, logfilename); } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, "-"); ccparray_put (&ccp, "--decrypt"); for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); if (filename) { ccparray_put (&ccp, "--"); ccparray_put (&ccp, filename); } ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (opt.gpg_program, argv, ((filename ? 0 : GNUPG_PROCESS_STDIN_KEEP) | GNUPG_PROCESS_STDOUT_PIPE), gnupg_spawn_helper, except, &proc); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, NULL, &stream, NULL); es_set_binary (stream); } else if (filename) { if (!strcmp (filename, "-")) stream = es_stdin; else stream = es_fopen (filename, "rb,sysopen"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); return err; } if (stream == es_stdin) es_set_binary (es_stdin); } else { stream = es_stdin; es_set_binary (es_stdin); } for (;;) { err = gpgtar_read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; err = extract (stream, dirname, tarinfo, header, extheader); if (err) goto leave; free_strlist (extheader); extheader = NULL; xfree (header); header = NULL; } if (proc) { err = es_fclose (stream); stream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } leave: notextracted = tarinfo->skipped_badname; notextracted += tarinfo->skipped_suspicious; notextracted += tarinfo->skipped_symlinks; notextracted += tarinfo->skipped_hardlinks; notextracted += tarinfo->skipped_other; if (opt.status_stream) es_fprintf (opt.status_stream, "[GNUPG:] GPGTAR_EXTRACT" " %llu %llu %lu %lu %lu %lu %lu\n", tarinfo->nextracted, notextracted, tarinfo->skipped_badname, tarinfo->skipped_suspicious, tarinfo->skipped_symlinks, tarinfo->skipped_hardlinks, tarinfo->skipped_other); if (notextracted && !opt.quiet) { log_info ("Number of files not extracted: %llu\n", notextracted); if (tarinfo->skipped_badname) log_info (" invalid name: %lu\n", tarinfo->skipped_badname); if (tarinfo->skipped_suspicious) log_info (" suspicious name: %lu\n", tarinfo->skipped_suspicious); if (tarinfo->skipped_symlinks) log_info (" symlink: %lu\n", tarinfo->skipped_symlinks); if (tarinfo->skipped_hardlinks) log_info (" hardlink: %lu\n", tarinfo->skipped_hardlinks); if (tarinfo->skipped_other) log_info (" other reason: %lu\n", tarinfo->skipped_other); } free_strlist (extheader); xfree (header); xfree (dirname); xfree (logfilename); if (stream != es_stdin) es_fclose (stream); return err; } diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 31bcd8d46..846008ee8 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -1,594 +1,600 @@ /* gpgtar-list.c - List a TAR archive * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include "../common/i18n.h" #include #include "gpgtar.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" static unsigned long long parse_xoctal (const void *data, size_t length, const char *filename) { const unsigned char *p = data; unsigned long long value; if (!length) value = 0; else if ( (*p & 0x80)) { /* Binary format. */ value = (*p++ & 0x7f); while (--length) { value <<= 8; value |= *p++; } } else { /* Octal format */ value = 0; /* Skip leading spaces and zeroes. */ for (; length && (*p == ' ' || *p == '0'); length--, p++) ; for (; length && *p; length--, p++) { if (*p >= '0' && *p <= '7') { value <<= 3; value += (*p - '0'); } else { log_error ("%s: invalid octal number encountered - assuming 0\n", filename); value = 0; break; } } } return value; } static tar_header_t parse_header (const void *record, const char *filename, tarinfo_t info) { const struct ustar_raw_header *raw = record; size_t n, namelen, prefixlen; tar_header_t header; int use_prefix; int anyerror = 0; info->headerblock = info->nblocks - 1; use_prefix = (!memcmp (raw->magic, "ustar", 5) && (raw->magic[5] == ' ' || !raw->magic[5])); for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) ; if (namelen == sizeof raw->name) { log_info ("%s: warning: name not terminated by a nul\n", filename); anyerror = 1; } for (n=namelen+1; n < sizeof raw->name; n++) if (raw->name[n]) { log_info ("%s: warning: garbage after name\n", filename); anyerror = 1; break; } if (use_prefix && raw->prefix[0]) { for (prefixlen=0; (prefixlen < sizeof raw->prefix && raw->prefix[prefixlen]); prefixlen++) ; if (prefixlen == sizeof raw->prefix) log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n", filename, info->headerblock); for (n=prefixlen+1; n < sizeof raw->prefix; n++) if (raw->prefix[n]) { log_info ("%s: warning: garbage after prefix\n", filename); anyerror = 1; break; } } else prefixlen = 0; header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen); if (!header) { log_error ("%s: error allocating header: %s\n", filename, gpg_strerror (gpg_error_from_syserror ())); return NULL; } if (prefixlen) { n = prefixlen; memcpy (header->name, raw->prefix, n); if (raw->prefix[n-1] != '/') header->name[n++] = '/'; } else n = 0; memcpy (header->name+n, raw->name, namelen); header->name[n+namelen] = 0; header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename); header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename); header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename); header->size = parse_xoctal (raw->size, sizeof raw->size, filename); header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename); /* checksum = */ switch (raw->typeflag[0]) { case '0': header->typeflag = TF_REGULAR; break; case '1': header->typeflag = TF_HARDLINK; break; case '2': header->typeflag = TF_SYMLINK; break; case '3': header->typeflag = TF_CHARDEV; break; case '4': header->typeflag = TF_BLOCKDEV; break; case '5': header->typeflag = TF_DIRECTORY; break; case '6': header->typeflag = TF_FIFO; break; case '7': header->typeflag = TF_RESERVED; break; case 'g': header->typeflag = TF_GEXTHDR; break; case 'x': header->typeflag = TF_EXTHDR; break; default: header->typeflag = TF_UNKNOWN; break; } /* Compute the number of data records following this header. */ if (header->typeflag == TF_REGULAR || header->typeflag == TF_EXTHDR || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; if (anyerror) { log_info ("%s: header block %llu is corrupt" " (size=%llu type=%d nrec=%llu)\n", filename, info->headerblock, header->size, header->typeflag, header->nrecords); /* log_printhex (record, RECORDSIZE, " "); */ } return header; } /* Parse the extended header. This funcion may modify BUFFER. */ static gpg_error_t parse_extended_header (const char *fname, char *buffer, size_t buflen, strlist_t *r_exthdr) { unsigned int reclen; unsigned char *p, *record; strlist_t sl; while (buflen) { record = buffer; /* Remember begin of record. */ reclen = 0; for (p = buffer; buflen && digitp (p); buflen--, p++) { reclen *= 10; reclen += (*p - '0'); } if (!buflen || *p != ' ') { log_error ("%s: malformed record length in extended header\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } p++; /* Skip space. */ buflen--; if (buflen + (p-record) < reclen) { log_error ("%s: extended header record larger" " than total extended header data\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } if (reclen < (p-record)+2 || record[reclen-1] != '\n') { log_error ("%s: malformed extended header record\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } record[reclen-1] = 0; /* For convenience change LF to a Nul. */ reclen -= (p-record); /* P points to the begin of the keyword and RECLEN is the * remaining length of the record excluding the LF. */ if (memchr (p, 0, reclen-1) && (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9))) { log_error ("%s: extended header record has an embedded nul" " - ignoring\n", fname); } else if (!strncmp (p, "path=", 5)) { sl = add_to_strlist_try (r_exthdr, p+5); if (!sl) return gpg_error_from_syserror (); sl->flags = 1; /* Mark as path */ } else if (!strncmp (p, "linkpath=", 9)) { sl = add_to_strlist_try (r_exthdr, p+9); if (!sl) return gpg_error_from_syserror (); sl->flags = 2; /* Mark as linkpath */ } buffer = p + reclen; buflen -= reclen; } return 0; } /* Read the next block, assuming it is a tar header. Returns a header * object on success in R_HEADER, or an error. If the stream is * consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case * of an error an error message is printed. If the header is an * extended header, a string list is allocated and stored at * R_EXTHEADER; the caller should provide a pointer to NULL. Such an * extended header is fully processed here and the returned R_HEADER * has then the next regular header. */ static gpg_error_t read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header, strlist_t *r_extheader) { gpg_error_t err; char record[RECORDSIZE]; int i; tar_header_t hdr; char *buffer; size_t buflen, nrec; err = read_record (stream, record); if (err) return err; info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i == RECORDSIZE) { /* All zero header - check whether it is the first part of an end of archive mark. */ err = read_record (stream, record); if (err) return err; info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i != RECORDSIZE) log_info ("%s: warning: skipping empty header\n", es_fname_get (stream)); else { /* End of archive - FIXME: we might want to check for garbage. */ *r_header = NULL; return 0; } } *r_header = parse_header (record, es_fname_get (stream), info); if (!*r_header) return gpg_error_from_syserror (); hdr = *r_header; if (hdr->typeflag != TF_EXTHDR || !r_extheader) return 0; /* Read the extended header. */ if (!hdr->nrecords) { /* More than 64k for an extedned header is surely too large. */ log_info ("%s: warning: empty extended header\n", es_fname_get (stream)); return 0; } if (hdr->nrecords > 65536 / RECORDSIZE) { /* More than 64k for an extedned header is surely too large. */ log_error ("%s: extended header too large - skipping\n", es_fname_get (stream)); return 0; } buffer = xtrymalloc (hdr->nrecords * RECORDSIZE); if (!buffer) { err = gpg_error_from_syserror (); log_error ("%s: error allocating space for extended header: %s\n", es_fname_get (stream), gpg_strerror (err)); return err; } buflen = 0; for (nrec=0; nrec < hdr->nrecords;) { err = read_record (stream, buffer + buflen); if (err) { xfree (buffer); return err; } info->nblocks++; nrec++; if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) buflen += RECORDSIZE; else buflen += (hdr->size % RECORDSIZE); } err = parse_extended_header (es_fname_get (stream), buffer, buflen, r_extheader); if (err) { free_strlist (*r_extheader); *r_extheader = NULL; } xfree (buffer); /* Now tha the extedned header has been read, we read the next * header without allowing an extended header. */ return read_header (stream, info, r_header, NULL); } /* Skip the data records according to HEADER. Prints an error message on error and return -1. */ static int skip_data (estream_t stream, tarinfo_t info, tar_header_t header) { char record[RECORDSIZE]; unsigned long long n; for (n=0; n < header->nrecords; n++) { if (read_record (stream, record)) return -1; info->nblocks++; } return 0; } static void print_header (tar_header_t header, strlist_t extheader, estream_t out) { unsigned long mask; char modestr[10+1]; int i; strlist_t sl; const char *name, *linkname; *modestr = '?'; switch (header->typeflag) { case TF_REGULAR: *modestr = '-'; break; case TF_HARDLINK: *modestr = 'h'; break; case TF_SYMLINK: *modestr = 'l'; break; case TF_CHARDEV: *modestr = 'c'; break; case TF_BLOCKDEV: *modestr = 'b'; break; case TF_DIRECTORY:*modestr = 'd'; break; case TF_FIFO: *modestr = 'f'; break; case TF_RESERVED: *modestr = '='; break; case TF_EXTHDR: break; case TF_GEXTHDR: break; case TF_UNKNOWN: break; case TF_NOTSUP: break; } for (mask = 0400, i = 0; i < 9; i++, mask >>= 1) modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-'; if ((header->typeflag & 04000)) modestr[3] = modestr[3] == 'x'? 's':'S'; if ((header->typeflag & 02000)) modestr[6] = modestr[6] == 'x'? 's':'S'; if ((header->typeflag & 01000)) modestr[9] = modestr[9] == 'x'? 't':'T'; modestr[10] = 0; /* FIXME: We do not parse the linkname unless its part of an * extended header. */ name = header->name; linkname = header->typeflag == TF_SYMLINK? "?" : NULL; for (sl = extheader; sl; sl = sl->next) { if (sl->flags == 1) name = sl->d; else if (sl->flags == 2) linkname = sl->d; } es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n", modestr, header->nlink, header->uid, header->gid, header->size, isotimestamp (header->mtime), name, linkname? " -> " : "", linkname? linkname : ""); } /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read from stdin. */ gpg_error_t gpgtar_list (const char *filename, int decrypt) { gpg_error_t err; estream_t stream = NULL; tar_header_t header = NULL; strlist_t extheader = NULL; struct tarinfo_s tarinfo_buffer; tarinfo_t tarinfo = &tarinfo_buffer; gnupg_process_t proc = NULL; memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (decrypt) { strlist_t arg; ccparray_t ccp; int except[2] = { -1, -1 }; const char **argv; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); - if (opt.status_fd != -1) + if (opt.status_fd) { static char tmpbuf[40]; + es_syshd_t hd; - snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%d", opt.status_fd); + snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); - except[0] = opt.status_fd; + es_syshd (opt.status_stream, &hd); +#ifdef HAVE_W32_SYSTEM + except[0] = hd.u.handle; +#else + except[0] = hd.u.fd; +#endif } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, "-"); ccparray_put (&ccp, "--decrypt"); for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); if (filename) { ccparray_put (&ccp, "--"); ccparray_put (&ccp, filename); } ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (opt.gpg_program, argv, ((filename ? 0 : GNUPG_PROCESS_STDIN_KEEP) | GNUPG_PROCESS_STDOUT_PIPE), gnupg_spawn_helper, except, &proc); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, NULL, &stream, NULL); es_set_binary (stream); } else if (filename) /* No decryption requested. */ { if (!strcmp (filename, "-")) stream = es_stdin; else stream = es_fopen (filename, "rb,sysopen"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); goto leave; } if (stream == es_stdin) es_set_binary (es_stdin); } else { stream = es_stdin; es_set_binary (es_stdin); } for (;;) { err = read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; print_header (header, extheader, es_stdout); if (skip_data (stream, tarinfo, header)) goto leave; free_strlist (extheader); extheader = NULL; xfree (header); header = NULL; } if (proc) { err = es_fclose (stream); stream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } leave: free_strlist (extheader); xfree (header); if (stream != es_stdin) es_fclose (stream); return err; } gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header, strlist_t *r_extheader) { return read_header (stream, info, r_header, r_extheader); } void gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out) { if (header && out) print_header (header, extheader, out); } diff --git a/tools/gpgtar.c b/tools/gpgtar.c index 492b3d5e5..dd269043f 100644 --- a/tools/gpgtar.c +++ b/tools/gpgtar.c @@ -1,697 +1,717 @@ /* gpgtar.c - A simple TAR implementation mainly useful for Windows. * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ /* GnuPG comes with a shell script gpg-zip which creates archive files in the same format as PGP Zip, which is actually a USTAR format. That is fine and works nicely on all Unices but for Windows we don't have a compatible shell and the supply of tar programs is limited. Given that we need just a few tar option and it is an open question how many Unix concepts are to be mapped to Windows, we might as well write our own little tar customized for use with gpg. So here we go. */ #include #include #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/openpgpdefs.h" #include "../common/init.h" #include "../common/strlist.h" #include "../common/comopt.h" #include "gpgtar.h" /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, aCreate = 600, aExtract, aEncrypt = 'e', aDecrypt = 'd', aSign = 's', aList = 't', oSymmetric = 'c', oRecipient = 'r', oUser = 'u', oOutput = 'o', oDirectory = 'C', oQuiet = 'q', oVerbose = 'v', oFilesFrom = 'T', oNoVerbose = 500, aSignEncrypt, oGpgProgram, oSkipCrypto, oOpenPGP, oCMS, oSetFilename, oNull, oUtf8Strings, oBatch, oAnswerYes, oAnswerNo, oStatusFD, oRequireCompliance, oWithLog, /* Compatibility with gpg-zip. */ oGpgArgs, oTarArgs, oTarProgram, /* Debugging. */ oDebug, oDryRun }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aCreate, "create", N_("create an archive")), ARGPARSE_c (aExtract, "extract", N_("extract an archive")), ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")), ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")), ARGPARSE_c (aSign, "sign", N_("create a signed archive")), ARGPARSE_c (aList, "list-archive", N_("list an archive")), ARGPARSE_group (301, N_("@\nOptions:\n ")), ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_s (oSetFilename, "set-filename", "@"), ARGPARSE_s_n (oOpenPGP, "openpgp", "@"), ARGPARSE_s_n (oCMS, "cms", "@"), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", "@"), ARGPARSE_s_n (oAnswerNo, "no", "@"), - ARGPARSE_s_i (oStatusFD, "status-fd", "@"), + ARGPARSE_s_s (oStatusFD, "status-fd", "@"), ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), ARGPARSE_s_n (oWithLog, "with-log", "@"), ARGPARSE_group (302, N_("@\nTar options:\n ")), ARGPARSE_s_s (oDirectory, "directory", N_("|DIRECTORY|change to DIRECTORY first")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), #ifdef HAVE_W32_SYSTEM ARGPARSE_s_n (oUtf8Strings, "utf8-strings", N_("-T reads UTF-8 encoded names")), #else ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"), #endif ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"), ARGPARSE_s_s (oTarArgs, "tar-args", "@"), ARGPARSE_s_s (oTarProgram, "tar", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_end () }; /* The list of commands and options for tar that we understand. */ static gpgrt_opt_t tar_opts[] = { ARGPARSE_s_s (oDirectory, "directory", N_("|DIRECTORY|extract files into DIRECTORY")), ARGPARSE_s_s (oFilesFrom, "files-from", N_("|FILE|get names to create from FILE")), ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")), ARGPARSE_end () }; /* Global flags. */ static enum cmd_and_opt_values cmd = 0; static int skip_crypto = 0; static const char *files_from = NULL; static int null_names = 0; static int any_debug; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGTAR@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: gpgtar [options] [files] [directories] (-h for help)"); break; case 41: p = _("Syntax: gpgtar [options] [files] [directories]\n" "Encrypt or sign files into an archive\n"); break; default: p = NULL; break; } return p; } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values c = *ret_cmd; if (!c || c == new_cmd) c = new_cmd; else if (c == aSign && new_cmd == aEncrypt) c = aSignEncrypt; else if (c == aEncrypt && new_cmd == aSign) c = aSignEncrypt; else { log_error (_("conflicting commands\n")); exit (2); } *ret_cmd = c; } /* Shell-like argument splitting. For compatibility with gpg-zip we accept arguments for GnuPG and tar given as a string argument to '--gpg-args' and '--tar-args'. gpg-zip was implemented as a Bourne Shell script, and therefore, we need to split the string the same way the shell would. */ static int shell_parse_stringlist (const char *str, strlist_t *r_list) { strlist_t list = NULL; const char *s = str; char quoted = 0; char arg[1024]; char *p = arg; #define addchar(c) \ do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0) #define addargument() \ do { \ if (p > arg) \ { \ *p = 0; \ append_to_strlist (&list, arg); \ p = arg; \ } \ } while (0) #define unquoted 0 #define singlequote '\'' #define doublequote '"' for (; *s; s++) { switch (quoted) { case unquoted: if (isspace (*s)) addargument (); else if (*s == singlequote || *s == doublequote) quoted = *s; else addchar (*s); break; case singlequote: if (*s == singlequote) quoted = unquoted; else addchar (*s); break; case doublequote: log_assert (s > str || !"cannot be quoted at first char"); if (*s == doublequote && *(s - 1) != '\\') quoted = unquoted; else addchar (*s); break; default: log_assert (! "reached"); } } /* Append the last argument. */ addargument (); #undef doublequote #undef singlequote #undef unquoted #undef addargument #undef addchar *r_list = list; return 0; } /* Like shell_parse_stringlist, but returns an argv vector instead of a strlist. */ static int shell_parse_argv (const char *s, int *r_argc, char ***r_argv) { int i; strlist_t list; if (shell_parse_stringlist (s, &list)) return 1; *r_argc = strlist_length (list); *r_argv = xtrycalloc (*r_argc, sizeof **r_argv); if (*r_argv == NULL) return 1; for (i = 0; list; i++) { gpgrt_annotate_leaked_object (list); (*r_argv)[i] = list->d; list = list->next; } gpgrt_annotate_leaked_object (*r_argv); return 0; } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { int no_more_options = 0; while (!no_more_options && gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oOutput: opt.outfile = pargs->r.ret_str; break; case oDirectory: opt.directory = pargs->r.ret_str; break; case oSetFilename: opt.filename = pargs->r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oFilesFrom: files_from = pargs->r.ret_str; break; case oNull: null_names = 1; break; case oUtf8Strings: opt.utf8strings = 1; break; case aList: case aDecrypt: case aEncrypt: case aSign: set_cmd (&cmd, pargs->r_opt); break; case aCreate: set_cmd (&cmd, aEncrypt); skip_crypto = 1; break; case aExtract: set_cmd (&cmd, aDecrypt); skip_crypto = 1; break; case oRecipient: add_to_strlist (&opt.recipients, pargs->r.ret_str); break; case oUser: opt.user = pargs->r.ret_str; break; case oSymmetric: set_cmd (&cmd, aEncrypt); opt.symmetric = 1; break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oSkipCrypto: skip_crypto = 1; break; case oOpenPGP: /* Dummy option for now. */ break; case oCMS: /* Dummy option for now. */ break; case oBatch: opt.batch = 1; break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; - case oStatusFD: opt.status_fd = pargs->r.ret_int; break; + case oStatusFD: opt.status_fd = pargs->r.ret_str; break; case oRequireCompliance: opt.require_compliance = 1; break; case oWithLog: opt.with_log = 1; break; case oGpgArgs:; { strlist_t list; if (shell_parse_stringlist (pargs->r.ret_str, &list)) log_error ("failed to parse gpg arguments '%s'\n", pargs->r.ret_str); else { if (opt.gpg_arguments) strlist_last (opt.gpg_arguments)->next = list; else opt.gpg_arguments = list; } } break; case oTarProgram: /* Dummy option. */ break; case oTarArgs: { int tar_argc; char **tar_argv; if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv)) log_error ("failed to parse tar arguments '%s'\n", pargs->r.ret_str); else { gpgrt_argparse_t tar_args; tar_args.argc = &tar_argc; tar_args.argv = &tar_argv; tar_args.flags = ARGPARSE_FLAG_ARG0; parse_arguments (&tar_args, tar_opts); gpgrt_argparse (NULL, &tar_args, NULL); if (tar_args.err) log_error ("unsupported tar arguments '%s'\n", pargs->r.ret_str); pargs->err = tar_args.err; } } break; case oDebug: any_debug = 1; break; case oDryRun: opt.dry_run = 1; break; default: pargs->err = 2; break; } } } /* gpgtar main. */ int main (int argc, char **argv) { gpg_error_t err; const char *fname; gpgrt_argparse_t pargs; gnupg_reopen_std (GPGTAR_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gnupg_init_signals (0, NULL); log_assert (sizeof (struct ustar_raw_header) == 512); /* Set default options */ - opt.status_fd = -1; + opt.status_fd = NULL; /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); if (log_get_errorcount (0)) exit (2); /* Get a log file from common.conf. */ if (!parse_comopt (GNUPG_MODULE_NAME_GPGTAR, any_debug) && comopt.logfile) log_set_file (comopt.logfile); /* 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]); } /* Set status stream for our own use of --status-fd. The original * status fd is passed verbatim to gpg. */ - if (opt.status_fd != -1) + if (opt.status_fd) { - int fd = translate_sys2libc_fd_int (opt.status_fd, 1); + int fd = -1; - if (!gnupg_fd_valid (fd)) - log_fatal ("status-fd is invalid: %s\n", strerror (errno)); +#ifdef HAVE_W32_SYSTEM + gnupg_fd_t hd; + + err = gnupg_sys2libc_fdstr (opt.status_fd, 1, &hd, NULL); + if ((uintptr_t)hd == 1) + fd = 1; + else if ((uintptr_t)hd == 2) + fd = 2; +#else + err = gnupg_sys2libc_fdstr (opt.status_fd, 1, NULL, &fd); +#endif + if (err) + log_fatal ("status-fd is invalid: %s\n", gpg_strerror (err)); if (fd == 1) { opt.status_stream = es_stdout; if (!skip_crypto) log_fatal ("using stdout for the status-fd is not possible\n"); } else if (fd == 2) opt.status_stream = es_stderr; else { - opt.status_stream = es_fdopen (fd, "w"); + es_syshd_t syshd; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = hd; +#else + syshd.type = ES_SYSHD_FD; + syshd.u.handle = fd; +#endif + opt.status_stream = es_sysopen (&syshd, "w"); if (opt.status_stream) es_setvbuf (opt.status_stream, NULL, _IOLBF, 0); } if (!opt.status_stream) { log_fatal ("can't open fd %d for status output: %s\n", fd, strerror (errno)); } } if (! opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (opt.verbose > 1) opt.debug_level = 1024; switch (cmd) { case aDecrypt: case aList: if (argc > 1) gpgrt_usage (1); fname = (argc && strcmp (*argv, "-"))? *argv : NULL; if (opt.filename) log_info ("note: ignoring option --set-filename\n"); if (files_from) log_info ("note: ignoring option --files-from\n"); if (cmd == aDecrypt) { err = gpgtar_extract (fname, !skip_crypto); if (err && !log_get_errorcount (0)) log_error ("extracting archive failed: %s\n", gpg_strerror (err)); } else { err = gpgtar_list (fname, !skip_crypto); if (err && !log_get_errorcount (0)) log_error ("listing archive failed: %s\n", gpg_strerror (err)); } break; case aEncrypt: case aSign: case aSignEncrypt: if ((!argc && !files_from) || (argc && files_from)) gpgrt_usage (1); if (opt.filename) log_info ("note: ignoring option --set-filename\n"); err = gpgtar_create (files_from? NULL : argv, files_from, null_names, !skip_crypto && (cmd == aEncrypt || cmd == aSignEncrypt), cmd == aSign || cmd == aSignEncrypt); if (err && log_get_errorcount (0) == 0) log_error ("creating archive failed: %s\n", gpg_strerror (err)); break; default: log_error (_("invalid command (there is no implicit command)\n")); err = 0; break; } if (opt.status_stream) { if (err || log_get_errorcount (0)) es_fprintf (opt.status_stream, "[GNUPG:] FAILURE - %u\n", err? err : gpg_error (GPG_ERR_GENERAL)); else es_fprintf (opt.status_stream, "[GNUPG:] SUCCESS\n"); } return log_get_errorcount (0)? 1:0; } /* Read the next record from STREAM. RECORD is a buffer provided by the caller and must be at least of size RECORDSIZE. The function return 0 on success and error code on failure; a diagnostic printed as well. Note that there is no need for an EOF indicator because a tarball has an explicit EOF record. */ gpg_error_t read_record (estream_t stream, void *record) { gpg_error_t err; size_t nread; nread = es_fread (record, 1, RECORDSIZE, stream); if (nread != RECORDSIZE) { err = gpg_error_from_syserror (); if (es_ferror (stream)) log_error ("error reading '%s': %s\n", es_fname_get (stream), gpg_strerror (err)); else log_error ("error reading '%s': premature EOF " "(size of last record: %zu)\n", es_fname_get (stream), nread); } else err = 0; return err; } /* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the name of the file used for diagnostics. */ gpg_error_t write_record (estream_t stream, const void *record) { gpg_error_t err; size_t nwritten; nwritten = es_fwrite (record, 1, RECORDSIZE, stream); if (nwritten != RECORDSIZE) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (stream), gpg_strerror (err)); } else err = 0; return err; } /* Return true if FP is an unarmored OpenPGP message. Note that this function reads a few bytes from FP but pushes them back. */ #if 0 static int openpgp_message_p (estream_t fp) { int ctb; ctb = es_getc (fp); if (ctb != EOF) { if (es_ungetc (ctb, fp)) log_fatal ("error ungetting first byte: %s\n", gpg_strerror (gpg_error_from_syserror ())); if ((ctb & 0x80)) { switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf)) { case PKT_MARKER: case PKT_SYMKEY_ENC: case PKT_ONEPASS_SIG: case PKT_PUBKEY_ENC: case PKT_SIGNATURE: case PKT_COMMENT: case PKT_OLD_COMMENT: case PKT_PLAINTEXT: case PKT_COMPRESSED: case PKT_ENCRYPTED: return 1; /* Yes, this seems to be an OpenPGP message. */ default: break; } } } return 0; } #endif diff --git a/tools/gpgtar.h b/tools/gpgtar.h index 9177fcfcb..26b405f0b 100644 --- a/tools/gpgtar.h +++ b/tools/gpgtar.h @@ -1,163 +1,163 @@ /* gpgtar.h - Global definitions for gpgtar * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #ifndef GPGTAR_H #define GPGTAR_H #include "../common/util.h" #include "../common/strlist.h" /* We keep all global options in the structure OPT. */ EXTERN_UNLESS_MAIN_MODULE struct { int verbose; unsigned int debug_level; int quiet; int dry_run; int utf8strings; const char *gpg_program; strlist_t gpg_arguments; const char *outfile; strlist_t recipients; const char *user; int symmetric; const char *filename; const char *directory; int batch; int answer_yes; int answer_no; - int status_fd; + const char *status_fd; estream_t status_stream; int require_compliance; int with_log; } opt; /* An info structure to avoid global variables. */ struct tarinfo_s { unsigned long long nblocks; /* Count of processed blocks. */ unsigned long long headerblock; /* Number of current header block. */ unsigned long long nextracted; /* Number of extracted files. */ unsigned long skipped_badname; unsigned long skipped_suspicious; unsigned long skipped_symlinks; unsigned long skipped_hardlinks; unsigned long skipped_other; }; typedef struct tarinfo_s *tarinfo_t; /* The size of a tar record. All IO is done in chunks of this size. Note that we don't care about blocking because this version of tar is not expected to be used directly on a tape drive in fact it is used in a pipeline with GPG and thus any blocking would be useless. */ #define RECORDSIZE 512 /* Description of the USTAR header format. */ struct ustar_raw_header { char name[100]; char mode[8]; char uid[8]; char gid[8]; char size[12]; char mtime[12]; char checksum[8]; char typeflag[1]; char linkname[100]; char magic[6]; char version[2]; char uname[32]; char gname[32]; char devmajor[8]; char devminor[8]; char prefix[155]; char pad[12]; }; /* Filetypes as defined by USTAR. */ typedef enum { TF_REGULAR, TF_HARDLINK, TF_SYMLINK, TF_CHARDEV, TF_BLOCKDEV, TF_DIRECTORY, TF_FIFO, TF_RESERVED, TF_GEXTHDR, /* Global extended header. */ TF_EXTHDR, /* Extended header. */ TF_UNKNOWN, /* Needs to be treated as regular file. */ TF_NOTSUP /* Not supported (used with --create). */ } typeflag_t; /* The internal representation of a TAR header. */ struct tar_header_s; typedef struct tar_header_s *tar_header_t; struct tar_header_s { tar_header_t next; /* Used to build a linked list of entries. */ unsigned long mode; /* The file mode. */ unsigned long nlink; /* Number of hard links. */ unsigned long uid; /* The user id of the file. */ unsigned long gid; /* The group id of the file. */ unsigned long long size; /* The size of the file. */ unsigned long long mtime; /* Modification time since Epoch. Note that we don't use time_t here but a type which is more likely to be larger that 32 bit and thus allows tracking times beyond 2106. */ typeflag_t typeflag; /* The type of the file. */ unsigned long long nrecords; /* Number of data records. */ char name[1]; /* Filename (UTF-8, dynamically extended). */ }; /*-- gpgtar.c --*/ gpg_error_t read_record (estream_t stream, void *record); gpg_error_t write_record (estream_t stream, const void *record); /*-- gpgtar-create.c --*/ gpg_error_t gpgtar_create (char **inpattern, const char *files_from, int null_names, int encrypt, int sign); /*-- gpgtar-extract.c --*/ gpg_error_t gpgtar_extract (const char *filename, int decrypt); /*-- gpgtar-list.c --*/ gpg_error_t gpgtar_list (const char *filename, int decrypt); gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header, strlist_t *r_extheader); void gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out); #endif /*GPGTAR_H*/